ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## Java专题十一(2):NIO [TOC] ### 0. NIO是什么 Non-blocking IO(非阻塞IO) > NIO可以让你非阻塞的使用IO,当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。 ### 1. 通道Channel > 通道代表面向实体的开放式连接,执行读写等IO操作,其中实体包括硬件设备,文件,网络套接字等 通道`Channel`与缓冲区`Buffer`密不可分,不同于IO从字符或字节流中读写操作,`Channel`既可以从`Buffer`中读取数据,也可以写入数据到`Buffer`中 **类图关系如下**: - Channel - InterruptibleChannel - FileChannel - SelectableChannel:能与选择器`Selector`结合实现多路IO复用 - SocketChannel - ServerSocketChannel - DatagramChannel - ReadableByteChannel:读取Channel数据到Buffer中, `int read(ByteBuffer dst)` - WritableByteChannel:写入Buffer数据到Channel中,` int write(ByteBuffer src)` `Channel`中一些重要方法: | 方法 | 说明 | | --- | --- | | `open()` | 用于创建Channel对象,如`ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();` | | `SelectionKey register(Selector sel, int ops)` | 只适用于`SelectableChannel`类型的通道,用于向Selector中注册操作,当通道中有注册过的操作时,可以对通道进行操作。ops包括4种: `SelectionKey.OP_ACCEPT`/`SelectionKey.OP_CONNECT`/`SelectionKey.OP_READ`/`SelectionKey.OP_WRITE` | | `SelectableChannel configureBlocking(boolean block)` | 只适用于`SelectableChannel`类型的通道,用于配置是否阻塞模式,如`serverSocketChannel.configureBlocking(false);` | | `int read(ByteBuffer dst)` | 将Channel里的数据读取到Buffer中 | | `int write(ByteBuffer src)` | 将Buffer中的数据写入到Channel中 | ### 2. 缓冲区Buffer > 相当于内存块,可读可写 **类图关系如下**: - Buffer - ByteBuffer: - HeapByteBuffer - CharBuffer: - ShortBuffer: - IntBuffer: - LongBuffer: - FloatBuffer: - DoubleBuffer: - DirectBuffer - DirectByteBuffer `ByteBuffer`中一些重要方法: | 方法 | 说明 | | --- | --- | | `allocate(int capacity)` | 用于创建容量为capacity的Buffer对象,如`ByteBuffer buffer = ByteBuffer.allocate(1024);` | | `boolean hasRemaining()` | 是否有元素在`position`和`limit`之间| | `ByteBuffer put(byte b)` | 将一个字节写入Buffer中 | | `ByteBuffer put(byte[] src)` | 将字节数组写入Buffer中 | | `byte get()` | 从Buffer中获取一个字节 | | `byte get(byte[] dst)` | 将Buffer中数据写入字节数组中 | | `ByteBuffer flip()` | 从写模式切换为读模式,`limit = positon; positon = 0; mark = -1` | | `ByteBuffer rewind()` | `position = 0; mark = -1;` | | `ByteBuffer clear()` | `position = 0; limit = capacity; mark = -1;` | | `ByteBuffer reset()` | `position = mark;` | `capacity`、`position`、`limit`说明: - capacity:缓冲区的大小,最多可以存放capacity个数据 - position:写模式下,代表写入了多少个数据,使用`flip()`切换为读模式时,position设为0 - limit:读模式下,代表能读取到的数据大小,使用`flip()`切换为读模式时,limit设为position ### 3. 选择器Selector > 一个`Selector`可以处理多个`Channel`,通常与`SelectableChannel`类型的`Channel`结合使用 `Selector`中一些重要方法: | 方法 | 说明 | | --- | --- | | `Selector open()` | 用于创建一个Selector对象,如`Selector selector = Selector.open();`| | `int select()` | 选择ready状态的操作集(keys set)| | `Set<SelectionKey> selectedKeys()` | 获取选择器已经选择的操作集(keys set)| ### 4.一个完整示例 ~~~ import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class Server { public static void main(String[] args) throws Exception{ ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 8017)); Selector selector = Selector.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while(true){ selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while(iterator.hasNext()){ SelectionKey key = iterator.next(); if(key.isAcceptable()){ System.out.println("key.isAcceptable()"); iterator.remove(); SocketChannel socketChannel = ((ServerSocketChannel)key.channel()).accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); }else if(key.isReadable()){ System.out.println("key.isReadable()"); iterator.remove(); SocketChannel socketChannel = (SocketChannel)key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); socketChannel.read(buffer); System.out.println(new String(buffer.array())); socketChannel.register(selector, SelectionKey.OP_WRITE); }else if(key.isWritable()){ System.out.println("key.isWritable()"); iterator.remove(); SocketChannel socketChannel = (SocketChannel)key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put(new String("i am server!!!").getBytes()); socketChannel.write(buffer); System.out.println(new String(buffer.array())); socketChannel.register(selector, SelectionKey.OP_READ); } } } } } ~~~ 1. 运行Server.java 2. 在CMD命令行输入`telnet 127.0.0.1 8017 `,换行,输入ab 3. 控制台输出如下: ~~~ key.isAcceptable() key.isReadable() a key.isWritable() i am server!!! key.isReadable() b key.isWritable() i am server!!! ~~~