Java NIO 应知应会 (一)
Java NIO
NIO(New IO),New 相对传统的阻塞IO而言,Java 1.4版本开始引入的一个新的I/O AP,它提供了一种不同于传统Java IO和网络API的编程模型。
NIO是面向缓存区,这使得它可以更好地支持非阻塞I/O操作,在网络编程方面尤其在处理大量连接时更加高效。
NIO经常被误解为Non-blocking IO,即非阻塞IO,这是不准确的,因为NIO提供了多种模式,包括阻塞和非阻塞。
本文主要内容包含NIO 核心组件
- Channel
- Buffer
- Selector
Channel
Channel是进行I/O操作的通道(IO不限于文件IO,网路IO等),它类似于传统的流(Stream),但有一些关键的区别
- Channel是全双工的,既可以读,也可以写
- Channel 所有的读写都是通过Buffer 进行的
- Channel 支持非阻塞模式,当然也支持阻塞模式(这是为什么NIO 翻译为Non-blocking IO不严谨的原因)
Channel 常见实现类
FileChannel :它提供了对文件进行随机访问的能力
DatagramChannel: 用于通过UDP协议在网络上发送和接收数据
SocketChannel:用于通过TCP协议在网络上建立客户端连接并进行通信
ServerSocketChannel:用于监听传入的TCP连接请求,类似于Web服务器的功能
简单的文件读取例子
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");FileChannel inChannel = aFile.getChannel();ByteBuffer buf = ByteBuffer.allocate(48);int bytesRead = inChannel.read(buf);while (bytesRead != -1) {System.out.println("Read " + bytesRead);buf.flip();while(buf.hasRemaining()){System.out.print((char) buf.get());}buf.clear();bytesRead = inChannel.read(buf);}aFile.close();
Buffer
Buffer 本质上是一块内存区域,可以用于读取数据和写入数据,比传统的流(Stream)更加高效。
Buffer 相关基本概念
- Capacity
缓冲区能够容纳的最大数据量 - Limit
缓冲区当前可读或可写的最大位置 - Position
当前读/写操作的位置。每次读/写操作后,位置会自动增加。
主要方法
-
flip(): 切换为读模式,调用flip()会将位置设置回 0,并将limit设置为刚刚位置所在的地方(position)
-
clear(): 清空缓冲区,位置将被设置回 0,limit将被设置为Capacity,实际上数据没有真正清除
-
compact(): 将未读的数据复制到缓冲区起始位置,然后将position设为未读数据的数量,limit保持不变。
-
mark() 和 reset(): 设置和重置position到之前标记的位置。
-
rewind(): 将position设为0,并保持limit不变,用于重新读取整个缓冲区。
-
hasRemaining(): 判断是否有剩余的元素可以读取。
-
remaining(): 返回还可以读取或写入的元素数量。
-
分配Buffer
ByteBuffer buf = ByteBuffer.allocate(48);
- 从Channel读取
int bytesRead = inChannel.read(buf); //read into buffer.
不同类型的 Buffer
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
以ByteBuffer为例,ByteBuffer其实是抽象类,它有两种实现DirectByteBuffer和HeapByteBuffer
-
DirectByteBuffer
直接在操作系统分配的内存中创建,而不是在Java堆空间中。这意味着它不受Java垃圾回收机制的影响。开发者需要手动释放内存,如果使用不当,可能会导致内存泄漏
ByteBuffer.allocateDirect(int capacity)方法来创建一个DirectByteBuffer实例
-
HeapByteBuffer
HeapByteBuffer是在Java堆内存中分配的。这意味着它可以被垃圾收集器管理。
性能特点:
在大多数情况下,对于小规模的数据处理,HeapByteBuffer的性能足够好,因为它避免了与操作系统交互带来的开销。在涉及大量I/O操作或需要高效内存访问的情况下,HeapByteBuffer可能不如DirectByteBuffer高效
使用ByteBuffer.allocate(int capacity)方法来创建一个HeapByteBuffer实例。
Selector
Selector可以检查一个或多个 Java NIO 通道(Channel)实例,
并确定哪些通道已准备好进行例如读取或写入操作,NIO selector 使用单线程就可以处理大量并发连接时。
- 多路复用:Selector可以同时监控多个通道的状态变化,如读就绪、写就绪等。
- 非阻塞I/O:结合非阻塞模式的通道使用,可以在没有数据可读或可写时立即返回,而不是阻塞等待。
- 事件驱动:当有感兴趣的事件发生时,Selector会通知应用程序,应用进行相应读写事件处理。
事件类型
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
使用步骤
- 创建 Selector
- 打开 Channel 并设置为非阻塞模式
- 将 Channel 注册到 Selector 上,并指定感兴趣的事件类型
在循环中调用 select() 方法,等待事件发生
处理事件
核心代码
Selector selector = Selector.open();channel.configureBlocking(false);SelectionKey key = channel.register(selector, SelectionKey.OP_READ);while(true) {int readyChannels = selector.select();if(readyChannels == 0) continue;Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while(keyIterator.hasNext()) {SelectionKey key = keyIterator.next();if(key.isAcceptable()) {// a connection was accepted by a ServerSocketChannel.} else if (key.isConnectable()) {// a connection was established with a remote server.} else if (key.isReadable()) {// a channel is ready for reading} else if (key.isWritable()) {// a channel is ready for writing}keyIterator.remove();}
}