JAVA IO的那些事

话题一:编码/转码

我们知道,在网络传输过程中,说到底,是要传输字节流的,字符流(Writer/Reader)不过是在字节流(InputStream/OutputStream)基础上做了一下封装而已,是JAVA在语法层面上给我们做的一个东西。

下面我们来先看看一段代码:


运行结果:

get info : 涓栫晫锛屼綘濂?

get info : 世界,你??

分析:

首先来说,S1是乱码,这个是好理解的,可是为什么S2也是乱码呢?

第一,S通过UTF-8编码形成字节数据B

第二,接收方拿到B,通过GBK来进行转码,得到S1,很显然会乱码。

更加重要的一点是:很可能B在GBK字符集中根本没有任何映射,那么此时GBK会指定一些特殊符号代替,比如?

第三,利用S1想还原原先的字节流的想法可能失败!

因为在上面的第二中,原来有些字节数据通过GBK的转码,可能被弄成?,而?在GBK中的字节数据和原先的字节数据就很可能不一样了!于是S2就这样被乱码了。

这说明,在编码的转换过程中,是很可能转不回来的!

话题二:网络交换数据的IO过程

从上面的图可以看出,JAVA本身不参与数据在网络上的传输,JAVA仅仅做的是,如果要发送数据,那么把字节流交给KERNEL;如果需要读取数据,那么从KERNEL中读取字节流。

发送与接收,如果采取一致的编码,那么就不会乱码!

在网络中传输的,应该是一种协议,不过是用字节流表达而已,比如,字节流的编码,大小,字节内容等。

话题三:JAVA BIO

Java BIO,即传统的IO,阻塞的IO,也是最常用,最基本的IO了,下面就来剖析下它!

  • Input/Output

Input:输入,把数据从磁盘、键盘、网络,内存中输入到程序中;

Output:输出,把程序中的数据输出到磁盘、显示器、网络、内存中;

可以说,没有IO,就没有结果,就没有价值!

  • 字节流 VS 字符流

字节流的源头是:InputStream/OutputStream

字符流的源头是:Reader/Writer

首先来说,我们一定得忘记字符流这回事,而是从字节流的角度进行理解IO。

之所以,存在字符流,是因为毕竟字节不是那么可视化,因此提供了把字节流转化成字符流的方式。

我们可以关注下Reader/Writer的子类:

可以清楚的看到,在InputStreamReader/OutputStreamWriter提供的构造方法中,接受一个字节流以及编码。其实,这就是字节流转化成字符流的桥梁。我们需要注意的是,在这个转化过程中,需要特别注意编码问题,最好就是手动的、明确的指定编码,而不是走默认的环境编码什么的。

  • DataInputStream/DataOutputStream

这种数据流有个特点,就是提供了各种readXXX/writeXXX方法,如下所示:

试想下,如果我们想通过网络,给对方发送个int,我们难道得自己将int转成4个字节来进行发送?这也太麻烦了吧,而DataInputStream已经提供好了众多数据格式的操作了。在Socket通信时,当我们需要封装Socket的IO操作时,可以考虑用这个数据流。

  • ByteArrayInputStream/ByteArrayOutputStream

内存字节流,这种流,会将内存中的字节包装形成流,延伸来看,String也可以先获取byte[],然后在转化成这种内存字节流。

  • ObjectInputStream/ObjectOutputStream

对象字节流,类比的思想,就是将对象包装形成字节流,然后,可以写入磁盘(持久化),可以通过网络发送出去(对方接受后,将字节流反序列化得到对象)。

  • 压缩流

ZipInputStream/GZinputStream/JarInputStream...

这些压缩流,可以套在文件字节流上,也可以套在网络字节流上(节省网络通信量)。

  • BufferedInputStream/BufferedOutputStream

如果我们想要Buffer功能,可以套一层Buffer流,Buffer往往会快点,这是为什么呢?

下面我们简单来看看BufferedOutputStream的实现:

可以看到在BufferedOutputStream内部维护了一个byte[],默认构造的情况下,大小是8192。

如果写,没有超出byte[]大小,那么写入byte[],这个过程当然相当快;如果超出了byte[]大小,那么flushBuffer,而flushBuffer才是真正写入的地方,而且是一次性的写入byte[]。

实际上,利用Buffer功能,将随机写变成了顺序写,当然快了!

  • PrintStream

打印字节流,我们经常使用到的System.out,System.err就是PrintStream对象,提供了众多数据格式的print方法。在重定向中,利用System.setOut/setErr,其实都是set到一个PrintStream上。

话题四:IO中的装饰思想

IO流涉及的类那么多,每种流都有自己的特点,如果希望多种功能/特点都聚集在一起,那么就要进行包装和装饰功能了。我们可以以最简单的BUFFER功能,来分析下这个装饰思想。

通过阅读FilterOutputStream类的源码,发现FilterOutputStream类持有OutputStream类的引用,它的每个方法,都没有干啥,就是对持有的OutputStream的引用进行调用对应方法罢了。

BufferedOutputStream extends FilterOutputStream,它内部通过自己的byte[],实现BUFFER功能。

到这里,我们好像发现点端倪了,开始有了些猜测:

那些有特点的IO类,是不是通过extends FilterXXXStream,然后在自己内部“耍些手段”来实现特定功能的呢?

通过上图,可以发现,的确有些有特点的IO类extends FilterXXXStream,比如DataXXXStream/BufferedXXXStream/PrintStream等。

FilterXXXStream就好像一层代理,虽然它什么都没有做,仅仅是通过引用去调用对应方法而已;这样看似无用,其实不然,比如想实现buffer功能,只需要extends FilterXXXStream,然后仅仅重写read/write方法即可,这样很多代码不需要再写,因为从FilterXXXStream上已经获得。Java这样的设计,巧妙!

话题五:flush() / close() 

流用完了,要关闭流时,我们经常想到要flush,要close,下面我们来分析分析它们!

2个标示接口,Closeable需要close,Flushable需要flush。

在OutputStream中,实际上它们是空的方法:

很显然,这是希望子类自己去flush,close。如果用到了Buffer机制,特别是通过修饰的功能拥有了BufferedXXXStream的特点,那么一定得注意flush/close,实际上是将byte[]中未处理的数据处理掉。

注意到FileXXXStream并没有去重写flush方法,如果extends FileXXXStream类,又没有提供flush的实现,那么将调用的是OutputStream中一个空的flush方法!比如SocketOutputStream extends FileOutputStream,却没有提供重写的flush方法,说明在SOCKET通信中,并不需要我们去调用flush方法,它是在内核层面上做的缓冲,而不是JAVA层面的。

因此,对一个IO类,要不要flush,要不要close,我们得看看它extends who?有buffer机制吗?

话题六:Java BIO vs NIO vs AIO

关于Java BIO/NIO,在前面的博客中已经有所介绍,大家可以参考下面的:

http://zhangfengzhe.blog.51cto.com/8855103/1715530

http://zhangfengzhe.blog.51cto.com/8855103/1726488

http://zhangfengzhe.blog.51cto.com/8855103/1712845

Java BIO:小张同学有一份快递,于是他去物流中转站去拿货,他在那里等着,一直等到快递来,这段期间,他哪里也不去。

Java NIO:小张同学,每天去一趟物流中转站,检测下是不是快递的货到了,如果到了,就带回来,如果没有则返回,明天继续。

Java AIO:小张同学并不去物流中转站,如果货到了,那么快递员“送货上门”。

上面的例子,形象的说明了BIO/NIO/AIO的特点和区别,胖哥说的好,存在就是有价值的,AIO是最新出来的东西,想法是好的,但是不一定是适合的。比如JDBC程序中,我们发了一个SQL,但是这是个AIO,那么麻烦了,发出这个SQL后,程序立刻向下执行,一段时间后SQL结果送来了,这个时候,我们要拿出保存有发出SQL那个时刻的一些信息。想一想,如果这样的AIO很多的话,程序会复杂很多,保存很多信息,回调很多方法。

这也再次说明了一个道理,如果它简单,那么它内在肯定很复杂,真是因为它内在的复杂,导致了外在的简单!


到这里,JAVA IO那些事就结束了,你学到了吗?





时间: 2024-10-26 06:02:51

JAVA IO的那些事的相关文章

Play生产模式下java.io.FileNotFoundException那点事

之前”用Scala+Play构建地理数据查询接口”那篇文章里,用到的数据是json格式的文本文件area.json,存放在conf/jsons文件夹下.最开始是放在public/文件夹下,在线上准生产模式下运行: activator dist 得到mosquito-1.0.zip压缩包,解压后: 去/bin目录下运行mosquito脚本报错: java.io.FileNotFoundException 然后就去解压的mosquito-1.0/看发现并没有public文件夹,由此可见public文

Java IO工作机制分析

Java的IO类都在java.io包下,这些类大致可分为以下4种: 基于字节操作的 I/O 接口:InputStream 和 OutputStream 基于字符操作的 I/O 接口:Writer 和 Reader 基于磁盘操作的 I/O 接口:File 基于网络操作的 I/O 接口:Socket 1 IO类库的基本结构 1.1 基于字节操作的IO接口 基于字节操作的IO接口分别是InputStream和OutputStream,InputStream的类结构图如下所示: 同InputStream

Java IO 装饰者模式

装饰模式(Decorator) 装饰模式又名包装(Wrapper)模式. 装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案. 装饰模式通过创建一个包装对象,也就是装饰,来包裹真实的对象. 装饰模式以对客户端透明的方式动态地给一个对象附加上更多的责任.换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同. 装饰模式可以在不创造更多子类的情况下,将对象的功能加以扩展. 装饰模式把客户端的调用委派到被装饰类.装饰模式的关键在于这种扩展是完全透明的. 装饰模式的角色 抽象构件角色(

java io --- Reader类

在前几篇文章中一直讲的都是InputStream,这是操作字节流的类,然而我们在程序中往往要从文件等stream中读取字符信息,如果只用InputStream能否读取字符信息呢?当然可以.但是这涉及到了一个编码和解码的问题,传输双方必须才用同一种编码方式才能正确接收,这就导致每次在传输时,传输方需要做这么几件事: 1)将需要传输的字符编码成指定字节 2)传输字节 接收方需要做这么几件事: 1)接收字节 2)将字节解码成对应的字符 我们看一下下面的例子: 我在对应目录有一个文件,这个文件是按照ut

Java IO RandomAccessFile 任意位置读/写

随机读写类 RandomAccessFile的唯一父类是Object,与其他流父类不同.是用来访问那些保存数据记录的文件的,这样你就可以用seek( )方法来访问记录,并进行读写了.这些记录的大小不必相同:但是其大小和位置必须是可知的. RandomAccessFile是不属于InputStream和OutputStream类系的.实际上,除了实现DataInput和DataOutput接口之外(DataInputStream和DataOutputStream也实现了这两个接口),它和这两个类系

Java IO流以及装饰器模式在其上的运用

流概述 Java中,流是一种有序的字节序列,可以有任意的长度.从应用流向目的地称为输出流,从目的地流向应用称为输入流. Java的流族谱 Java的 java.io 包中囊括了整个流的家族,输出流和输入流的谱系如下所示: InputStream和OutputStream InputStream和OutputStream分别是输入输出流的顶级抽象父类,只定义了一些抽象方法供子类实现. 在输出流OutputStream中,如果你需要向一个输出流写入数据,可以调用 void write(int b)

java序列化Serializable那些事

串行化也叫序列化,就是将实例的状态转化成文本(或二近制)的形式,以便永久保存(所以有时候也叫持久化,或者信息的冷藏等等)或在网间传递.也就是说,如果一个类的实例需要持久化或者需要在网间传递的时候,就用到了串行化  Serialization是指把类或者基本的数据类型持久化(persistence)到数据流(Stream)中,包括文件.字节流.网络数据流.         JAVA中实现serialization主要靠两个类:ObjectOuputStream和ObjectInputStream.

《java与设计模式》之装饰模式详解&Java IO中的装饰器模式

1 概述 在一个项目中,你会有非常多的因素考虑不到,特别是业务的变更,不时的冒出一个需求是很正常的情况.有三个继承关系的类:Father.Son.GrandSon,我们要在Son类上增强一些功能怎么办?给Son类增加方法吗?那对GrandSon的影响呢?特别是对GrandSon有多个的情况,你会怎么办?认真看完本文,你会找到你的答案. JavaIO中,像下面的嵌套语句是不是很常见,为什么要怎样定义呢?理解装饰模式后,你会找到答案. DataInputStream in = new DataInp

Java调试那点事

转自: http://yq.aliyun.com/articles/56?hmsr=toutiao.io&spm=5176.100240.searchblog.18&utm_medium=toutiao.io&utm_source=toutiao.io Java调试那点事 该文章来自于阿里巴巴技术协会(ATA)精选文章. Java调试概述 程序猿都调式或者debug过Java代码吧?都体会过被PM,PD,测试,业务同学们围观debug吧?说调试,先看看调试严格定义是什么.引用Wik