首先,传统的java.io包基于流模型实现,提供了我们最熟知的一些IO功能,比如File抽象、输入输出流等,交互方式是同步、阻塞的方式。也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线形顺序。
java.io包的好处是代码比较简单、直观,缺点则是IO效率和扩展存在局限性,容易成为应用性能的瓶颈。
很多时候,人们也把java.net下面提供的部分网络API,比如Socket、ServerSocket、HttpURLConnection也归类到同步阻塞IO类库,因为网络通信同样是IO行为。
第二、在Java 1.4中引入了NIO框架(java.nio包),提供了Channel、Selector、Buffer等新的抽象,可以构建多路复用的、同步非阻塞IO程序,同时提供了更接近操作系统底层的高性能数据操作方式,
第三、在Java 7中,NIO有了进一步的改进,也就是NIO 2,引入异步非阻塞IO方式,也有很多人叫它AIO(Asynchronous IO)。异步IO操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。
知识扩展
1、澄清概念
同步(synchronous) vs. 异步(asynchronous):同步是一种可靠的有序运行机制。当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步;而异步则相反,其它任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系。
阻塞(blocking) vs. 非阻塞(non-blocking):在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其它任务,只有当条件就绪才能继续;而非阻塞则是不管IO操作是否结束,直接返回,相应操作在后台继续处理。
2、java.io总结
下图是一个简化的类图,包含了日常开发应用较多的类型和结构关系:
![](/qrcode.jpg)
IO不仅仅是对文件的操作,网络编程中,比如Socket通信,都是典型的IO操作目标。
输入输出流(InputStream/OutputStream)是用于读取或写入字节的,例如操作图片文件。Reader/Writer则是用于操作字符,增加了字符编/解码等功能,适用于类似从文件中读取或写入文本信息。本质上计算机操作的都是字节,不管是网络通信还是文件读取,Reader/Writer相当于构建了应用逻辑和原始数据之间的桥梁。
BufferedOutputStream等带缓冲区的实现,可以避免频繁的磁盘读写,进而提高IO处理效率。这种设计利用了缓冲区,将批量数据进行一次操作。但在使用中千万别忘了flush。
从上图中可以看出,很多IO工具类都实现了Closable接口,因为需要进行资源的释放。比如,打开FileInputStream,它就会获取相应的文件描述符(FileDescriptor),需要利用try-with-resources、try-finally等机制保证FileInputStream被明确关闭,进而相应文件描述符也会失效,否则将导致资源无法被释放。
3、java.nio概述
NIO的主要组成部分:
Buffer,高效的数据容器,除了布尔类型,所有原始数据类型都有相应的Buffer实现。
Channel,类似在Linux之类操作系统上看到的文件描述符,是NIO中被用来支持批量式IO操作的一种抽象。File或者Socket,通常被认为是比较高层次的抽象,而Channel则是更加接近操作系统底层的一种抽象,这也使得NIO得以充分利用现代操作系统底层机制,获得特定场景的性能优化,例如DMA(Direct Memory Access)等。不同层次的抽象是相互关联的,我们可以通过Socket获取Channel,反之亦然。
Selector,是NIO实现多路复用的基础,它提供了一种高效的机制,可以检测到注册在Selector上的多个Channel中,是否有Channel处于就绪状态,进而实现了单线程对多Channel的高效管理。Selector同样是基于底层操作系统机制的,不同模式、不同版本都存在区别。
Charset,提供Unicode字符串定义,NIO也提供了相应的编/解码器等。例如,通过下面的方式进行字符串到ByteBuffer的转换:
Charset.defaultCharset().encode("Hello world!");