Java 提供了哪些 IO 方式? NIO 如何实现多路复用?
Java IO 方式有很多种,基于不同的 IO 抽象模型和交互方式,可以进行简单区分。 首先,传统的 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 操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里, 当后台处理完成,操作系统会通知相应线程进行后续工作。
NIO是一种非阻塞式I/O;采用了双向通道进行数据传输,NIO基于Channel和Buffer(缓冲区)进⾏操作,数据总是从通道读取到缓冲区中,或者从缓冲区 写⼊入到通道中。Selector(选择区 [阻塞] )用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。 利用单线程轮询事件的机制,有效避免大量客户端连接时,频繁线程切换带来的问题。
IO、NIO
IO 概览:
- IO 不仅仅是对文件的操作,网络编程中,比如 Socket 通信,都是典型的 IO 操作目标。
- 输入流、输出流(InputStream/OutputStream)是用于读取或写入字节的,例如操作图片文件。
- Reader/Writer 用于操作字符,增加了字符编解码等功能,适用于类似从文件中读取或者写入文本信息。本质上计算机操作的都是字节,不管是网络通信还是文件读取,Reader/Writer 相当于构建了应用逻辑和原始数据之间的桥梁。
- BufferedOutputStream 等带缓冲区的实现,可以避免频繁的磁盘读写,进而提高 IO 处理效率。这种设计利用了缓冲区,将批量数据进行一次操作,但在使用中千万别忘了 flush。
- 很多 IO 工具类都实现了 Closeable 接口,因为需要进行资源的释放。
NIO 概览:
- Buffer,高效的数据容器,除了布尔类型,所有原始数据类型都有相应的 Buffer 实现。
- Channel,类似在 Linux 之类操作系统上看到的文件描述符,是 NIO 中被用来支持批量式IO 操作的一种抽象。
- File 或者 Socket,通常被认为是比较高层次的抽象,而 Channel 则是更加操作系统底层的一种抽象,这也使得 NIO 得以充分利用现代操作系统底层机制,获得特定场景的性能优化,例如,DMA(Direct Memory Access)等。不同层次的抽象是相互关联的,我们可以通过Socket 获取 Channel,反之亦然。
- Selector,是 NIO 实现多路复用的基础,它提供了一种高效的机制,可以检测到注册在Selector 上的多个 Channel 中,是否有 Channel 处于就绪状态,进而实现了单线程对多Channel 的高效管理。
- Chartset,提供 Unicode 字符串定义,NIO 也提供了相应的编解码器等,例如,Charset.defaultCharset().encode(“Hello world!"))进行字符串到 ByteBuffer 的转换
IO 适用场景
如果只有少量的连接,而这些连接每次要发送大量的数据,这时候传统的IO更更合适。使⽤用哪种处理数据,需要在数据的响应等待时间和检查缓冲区数据的时间上作⽐比较来权衡选择。
NIO 适用场景
如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,这时候用NIO处理数据可能是个很好的选择。(适⽤用于小数据多连接)
NIO原理:
- 由一个专门的线程来处理所有的 IO 事件,并负责分发。
- 事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
- 线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。