简介: I/O 问题能够说是当今互联网 Web 应用中所面临的主要问题之一,由于当前在这个海量数据时代,数据在网络中随处流动。这个流动的过程当中都涉及到 I/O 问题,能够说大部分 Web 应用系统的瓶颈都是 I/O 瓶颈。本文的目的正是分析 I/O 的内在工做机制,你将了解到:Java 的 I/O 类库的基本架构;磁盘 I/O 工做机制;网络 I/O 的工做机制;其中以网络 I/O 为重点介绍 Java Socket 的工做方式;你还将了解到 NIO 的工做方式,还有同步和异步以及阻塞与非阻塞的区别,最后咱们将介绍一些经常使用的关于 I/O 的优化技巧。java
I/O 问题是任何编程语言都没法回避的问题,能够说 I/O 问题是整我的机交互的核心问题,由于 I/O 是机器获取和交换信息的主要渠道。在当今这个数据大爆炸时代,I/O 问题尤为突出,很容易成为一个性能瓶颈。正因如此,因此 Java 在 I/O 上也一直在作持续的优化,如从 1.4 开始引入了 NIO,提高了 I/O 的性能。关于 NIO 咱们将在后面详细介绍。网络
Java 的 I/O 操做类在包 java.io 下,大概有将近 80 个类,可是这些类大概能够分红四组,分别是:架构
基于字节操做的 I/O 接口:InputStream 和 OutputStreamapp
基于字符操做的 I/O 接口:Writer 和 Reader异步
基于磁盘操做的 I/O 接口:File编程语言
基于网络操做的 I/O 接口:Socket性能
前两组主要是根据传输数据的数据格式,后两组主要是根据传输数据的方式,虽然 Socket 类并不在 java.io 包下,可是我仍然把它们划分在一块儿,由于我我的认为 I/O 的核心问题要么是数据格式影响 I/O 操做,要么是传输方式影响 I/O 操做,也就是将什么样的数据写到什么地方的问题,I/O 只是人与机器或者机器与机器交互的手段,除了在它们可以完成这个交互功能外,咱们关注的就是如何提升它的运行效率了,而数据格式和传输方式是影响效率最关 键的因素了。咱们后面的分析也是基于这两个因素来展开的。优化
基于字节的 I/O 操做接口输入和输出分别是:InputStream 和 OutputStream,InputStream 输入流的类继承层次以下图所示:
图 1. InputStream 相关类层次结构(查看大图)
输入流根据数据类型和操做方式又被划分红若干个子类,每一个子类分别处理不一样操做类型,OutputStream 输出流的类层次结构也是相似,以下图所示:
图 2. OutputStream 相关类层次结构(查看大图)
这里就不详细解释每一个子类如何使用了,若是不清楚的话能够参考一下 JDK 的 API 说明文档,这里只想说明两点,一个是操做数据的方式是能够组合使用的,如这样组合使用
OutputStream out = new BufferedOutputStream(new ObjectOutputStream(new FileOutputStream("fileName"))
;
还有一点是流最终写到什么地方必需要指定,要么是写到磁盘要么是写到网络中,其实从上面的类图中咱们发现,写网络实际上也是写文件,只不过写网络还 有一步须要处理就是底层操做系统再将数据传送到其它地方而不是本地磁盘。关于网络 I/O 和磁盘 I/O 咱们将在后面详细介绍。
不论是磁盘仍是网络传输,最小的存储单元都是字节,而不是字符,因此 I/O 操做的都是字节而不是字符,可是为啥有操做字符的 I/O 接口呢?这是由于咱们的程序中一般操做的数据都是以字符形式,为了操做方便固然要提供一个直接写字符的 I/O 接口,如此而已。咱们知道字符到字节必需要通过编码转换,而这个编码又很是耗时,并且还会常常出现乱码问题,因此 I/O 的编码问题常常是让人头疼的问题。关于 I/O 编码问题请参考另外一篇文章 《深刻分析Java中的中文编码问题》。
下图是写字符的 I/O 操做接口涉及到的类,Writer 类提供了一个抽象方法 write(char cbuf[], int off, int len) 由子类去实现。
图 3. Writer 相关类层次结构(查看大图)
读字符的操做接口也有相似的类结构,以下图所示:
图 4.Reader 类层次结构(查看大图)
读字符的操做接口中也是 int read(char cbuf[], int off, int len),返回读到的 n 个字节数,不论是 Writer 仍是 Reader 类它们都只定义了读取或写入的数据字符的方式,也就是怎么写或读,可是并无规定数据要写到哪去,写到哪去就是咱们后面要讨论的基于磁盘和网络的工做机 制。
另外数据持久化或网络传输都是以字节进行的,因此必需要有字符到字节或字节到字符的转化。字符到字节须要转化,其中读的转化过程以下图所示:
InputStreamReader 类是字节到字符的转化桥梁,InputStream 到 Reader 的过程要指定编码字符集,不然将采用操做系统默认字符集,极可能会出现乱码问题。StreamDecoder 正是完成字节到字符的解码的实现类。也就是当你用以下方式读取一个文件时:
try { StringBuffer str = new StringBuffer(); char[] buf = new char[1024]; FileReader f = new FileReader("file"); while(f.read(buf)>0){ str.append(buf); } str.toString(); } catch (IOException e) {} |
FileReader 类就是按照上面的工做方式读取文件的,FileReader 是继承了 InputStreamReader 类,其实是读取文件流,而后经过 StreamDecoder 解码成 char,只不过这里的解码字符集是默认字符集。
写入也是相似的过程以下图所示:
经过 OutputStreamWriter 类完成,字符到字节的编码过程,由 StreamEncoder 完成编码过程。