JAVA IO的那些事

简介:

话题一:编码/转码

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


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

wKioL1YrPTqCuY0dAAHxAGvG_sU621.jpg


运行结果:

get info : 涓栫晫锛屼綘濂?

get info : 世界,你??



分析:


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


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


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


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


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


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



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



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

wKioL1Z-erijYrS_AAA2tUp6-xI359.png

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


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


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


话题三:JAVA BIO

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


  • Input/Output

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

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

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


  • 字节流 VS 字符流

字节流的源头是:InputStream/OutputStream

字符流的源头是:Reader/Writer


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


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


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

wKiom1Z_RsmiB2eiAAATKDisQrQ181.png


wKioL1Z_R0Cw-8_GAAASUbJR9Z0058.png


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


  • DataInputStream/DataOutputStream

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

wKiom1Z_SsDgL4XJAAA4eM9PR64233.png

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


  • ByteArrayInputStream/ByteArrayOutputStream

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

wKiom1Z_TZ2ipBPcAAAG7R8NSHg480.png


  • ObjectInputStream/ObjectOutputStream

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


  • 压缩流

ZipInputStream/GZinputStream/JarInputStream...

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


  • BufferedInputStream/BufferedOutputStream

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


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

wKioL1Z_UaTBpxWVAAAEfYc6N48524.png

wKioL1Z_UcOh8gWUAAAKdQDFdAc565.png

wKioL1Z_USKRssWaAAAWEDZ_MJQ518.png

wKioL1Z_UUyypRrQAAAR3By3SyA028.png

可以看到在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功能,来分析下这个装饰思想。

wKioL1Z_f6yBMgFSAAAbLj3oWMg281.png


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


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


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

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


wKioL1Z_go3iS-kwAABlEmJUvlg281.png


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


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


话题五:flush() / close() 

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


wKiom1Z_h2zxiv0cAAAJbD4eLlk375.png

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


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

wKioL1Z_iLPwCRfyAAAIEyZFZSw969.png

wKiom1Z_iKbxJcA3AAAG_JwvX44604.png


很显然,这是希望子类自己去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那些事就结束了,你学到了吗?


本文转自zfz_linux_boy 51CTO博客,原文链接:http://blog.51cto.com/zhangfengzhe/1728780,如需转载请自行联系原作者

相关文章
|
1月前
|
存储 Java 数据处理
|
1月前
|
Java API
java中IO与NIO有什么不同
java中IO与NIO有什么不同
|
3月前
|
存储 Java 数据安全/隐私保护
从零开始学习 Java:简单易懂的入门指南之IO字符流(三十一)
从零开始学习 Java:简单易懂的入门指南之IO字符流(三十一)
|
3月前
|
存储 移动开发 Java
从零开始学习 Java:简单易懂的入门指南之IO字节流(三十)
从零开始学习 Java:简单易懂的入门指南之IO字节流(三十)
|
3月前
|
存储 算法 Java
从零开始学习 Java:简单易懂的入门指南之IO序列化、打印流、压缩流(三十三)
从零开始学习 Java:简单易懂的入门指南之IO序列化、打印流、压缩流(三十三)
|
18天前
|
存储 Java
探索 Java IO 流的多种实现方式
【4月更文挑战第4天】Java IO 流是处理输入输出的关键组件,包括文件流(FileInputStream/FileOutputStream)、字符流(FileReader/FileWriter)、缓冲区流(BufferedInputStream/BufferedOutputStream)、转换流(InputStreamReader/OutputStreamWriter)、数据流(DataInputStream/DataOutputStream)、对象流(ObjectInputStream/ObjectOutputStream)、随机访问文件流(RandomAccessFile)和管道流。
|
30天前
|
Java 关系型数据库 MySQL
Flink1.18.1和CDC2.4.1 本地没问题 提交任务到服务器 报错java.lang.NoClassDefFoundError: Could not initialize class io.debezium.connector.mysql.MySqlConnectorConfig
【2月更文挑战第33天】Flink1.18.1和CDC2.4.1 本地没问题 提交任务到服务器 报错java.lang.NoClassDefFoundError: Could not initialize class io.debezium.connector.mysql.MySqlConnectorConfig
49 2
|
1月前
|
Java
|
2月前
|
Java 数据处理
如何玩转Java IO?
【2月更文挑战第7天】
219 0
如何玩转Java IO?
|
2月前
|
缓存 分布式计算 Java
Java基础深化和提高-------IO流
Java基础深化和提高-------IO流
108 0