NIO
非阻塞 IO
NIO 是 Java 提供的替代 BIO 的相关 API
NIO 三大核心组件
Buffr 缓冲区
Channel 通道
Selector 选择器
Buffer 缓冲区
Java 提供 Buffer API, 可以让我们更轻松的使用内存块
使用 Buffer 对象,对数据进行写入和读取
- 将数据写入缓冲区
- 调用 buffer.flip(),转换为读取模式
- 缓冲区读取数据
- 调用 buffer.clear()或 buffer.compact() 清楚缓冲区
Buffer 的三个属性
capacity 容量: 缓冲区内存块大小
position 位置: 写入或读取时的位置
limit 限制: 限制每次读取或者写入的大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class BufferDemo { public static void main(String[] args) { ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4); System.out.println(String.format("初始化:capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(), byteBuffer.position(), byteBuffer.limit())); byteBuffer.put((byte) 1); byteBuffer.put((byte) 2); byteBuffer.put((byte) 3); System.out.println(String.format("写入3字节后,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(), byteBuffer.position(), byteBuffer.limit()));
System.out.println("#######开始读取"); byteBuffer.flip(); byte a = byteBuffer.get(); System.out.println(a); byte b = byteBuffer.get(); System.out.println(b); System.out.println(String.format("读取2字节数据后,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(), byteBuffer.position(), byteBuffer.limit()));
byteBuffer.compact(); byteBuffer.put((byte) 3); byteBuffer.put((byte) 4); byteBuffer.put((byte) 5); System.out.println(String.format("最终的情况,capacity容量:%s, position位置:%s, limit限制:%s", byteBuffer.capacity(), byteBuffer.position(), byteBuffer.limit()));
} }
|
Buffer 可以直接获取直接内存
ByteBuffer directByteBuffer=ByteBuffer.allocateDirect(n);
内部有一个回收对象, 可以进行垃圾回收, 否则 JVM 的垃圾回收无法管理堆外内存
使用直接内存, 可以减少一次数据拷贝, 如果使用 JVM 堆内存,写入时用堆内存会复制一份数据到堆外内存。 因为JVM进行GC时会移动数据的位置, 导致IO写入异常
Channel 通道
通道从 ByteBuffer 中读取数据或者写入数据
Channel 四种实现类型
1. FileChannel: 用于文件的数据读写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| RandomAccessFile aFile = new RandomAccessFile("test.txt","rw"); FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48); int bytesRead = inChannel.read(buf);
String newData = "New String to write to file..." + System.currentTimeMillis(); ByteBuffer buf = ByteBuffer.allocate(48); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while(buf.hasRemaining()){ channel.write(buf); }
channel.close();
|
2. DatagramChannel: 用于 UDP 的数据读写。
3. SocketChannel: 用于 TCP 的数据读写。
4. ServerSocketChannel: 监听 TCP 链接请求,每个请求会创建会一个 SocketChannel。
1 2 3 4 5 6 7 8 9
| ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(9999)); serverSocketChannel.configureBlocking(false); while(true){ SocketChannel socketChannel = serverSocketChannel.accept(); if(socketChannel != null){ } }
|
Selector 选择器
可以检查一个或多个 NIO 通道,实现单个线程管理多个通道,从而管理多个网络连接
比如:当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务,不会阻塞
一个通道继承了 SelectableChannel,那么他就可以被 Selector 复用
一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。
通道和选择器之间的关系,使用注册的方式完成。SelectableChannel 可以被注册到 Selector 对象上,在注册的时候,需要指定通道的哪些操作,是 Selector 感兴趣的。
使用 Channel.register(Selector sel,int ops)方法, 将通道注册到选择器上,这里的操作指的是当前通道已经准备就绪,能够进行的操作类型
int ops 包括
- 可读 : SelectionKey.OP_READ
- 可写 : SelectionKey.OP_WRITE
- 连接 : SelectionKey.OP_CONNECT
- 接收 : SelectionKey.OP_ACCEPT
selector.select();
查找准备就绪的通道
Selector 使用流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(SystemConfig.SOCKET_SERVER_PORT));
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
while (selector.select() > 0) { Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator(); while (selectedKeys.hasNext()) { SelectionKey selectedKey = selectedKeys.next(); if (selectedKey.isAcceptable()) { SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } else if (selectedKey.isReadable()) { SocketChannel socketChannel = (SocketChannel) selectedKey.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); int length = 0; while ((length = socketChannel.read(byteBuffer)) != -1){ byteBuffer.flip(); System.out.println(new String(byteBuffer.array(), 0, length)); byteBuffer.clear(); } socketChannel.close(); } selectedKeys.remove(); } }
serverSocketChannel.close(); }
|
注意:要注册到选择器, 通道必须是非租塞的
Reactor 模式
基于 Java NIO, 在此基础, 抽象出来两个组件–Reactor 和 Handler
- Reactor: 负责响应 IO 事件,当检测到新的时间, 发送给相应的 Handler
- Handler: 执行处理