Java NIO原理图文分析及代码实现
简介
Java NIO(Non-blocking I/O)是一种可替代Java标准I/O的I/O API。相比传统的I/O,Java NIO提供的I/O操作更快速、更灵活,并且支持更多的操作(如块传输和多路复用)。
基本组成部分
Java NIO的核心组件主要包含以下几个部分:
- Channel(通道):基本的I/O操作(如写入和读取)在通道中完成。
- Buffer(缓冲区):通道读写数据的存储区域,读取数据时首先将数据读入缓冲区,写入数据时首先将数据写入缓冲区。
- Selector(选择器):用于多路复用I/O操作,同时能够检测某个通道上是否有事件发生。
Channel
Java NIO中的通道(Channel)类似于流(Stream)的概念,用于进行数据的读取和写入。不过,与流不同的是:
- 通道是双向的,可以在一个通道上进行读取和写入操作。
- 通道可以通过多个线程进行操作(前提是线程安全)。
Java NIO中的通道主要分为以下几类:
- FileChannel:用于读取、写入和映射文件的通道。
- DatagramChannel:通过UDP协议读取或写入网络数据。
- SocketChannel:通过TCP协议进行网络连接的通道。
- ServerSocketChannel:可以监听新进来的TCP连接,并且为每个新连接创建一个通道。
Buffer
缓冲区(Buffer)实际上就是一个内存块,通道读取或写入时通过缓冲区读写数据。在Java NIO中,缓冲区主要分为以下几种类型:
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
这些缓冲区类型的创建方法基本相同,可以通过以下方法进行创建:
Buffer buffer = ByteBuffer.allocate(1024);
缓冲区从内部来说,实际上就是一个数组。除了存储数据之外,缓冲区还提供了三个核心操作:
- 写入数据到缓冲区:
put()
- 从缓冲区读取数据:
get()
- 读写模式切换:
flip()
其中,读写模式切换是这样用的:
// 读模式
buffer.flip();
// 写模式
buffer.clear();
Selector
多路复用器(Selector)是一种可以同时处理多个通道的Java NIO组件。多路复用器基于底层操作系统提供的select()方法(或者epoll、KQueue等方法),可以检测某些事件是否发生。
多路复用器的运行主要流程如下:
- 创建多路复用器(
Selector
)。 - 将各个通道注册到多路复用器上(
SocketChannel.register()
)。 - 轮询多路复用器中发生事件的通道(
Selector.select()
)。 - 处理具体的事件(如连接、读取、写入等)。
示例说明
示例1:使用FileChannel读取文件
public static void readFile(String filePath) throws IOException {
// 获取文件的FileChannel
File file = new File(filePath);
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
FileChannel fileChannel = randomAccessFile.getChannel();
// 创建buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (fileChannel.read(buffer) != -1) {
// 切换到读模式
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
// 切换到写模式
buffer.clear();
}
// 关闭FileChannel
fileChannel.close();
}
以上代码中,我们首先通过RandomAccessFile
获取到需要读取的文件,然后通过getChannel()
方法获取到文件的FileChannel
。接下来,我们创建一个ByteBuffer
缓冲区,通过FileChannel
的read()
方法从文件通道读取数据,并通过ByteBuffer
的flip()
方法将缓冲区切换到读模式。最后,我们通过ByteBuffer
的get()
方法从缓冲区读取数据,并在控制台上输出。当ByteBuffer
中没有数据时,我们将缓冲区切换到写模式,并继续执行下一轮读操作,直到文件读取完毕。
示例2:使用ServerSocketChannel监听并处理连接
public static void startServer(int port) throws IOException {
// 创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(port));
// 设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 创建Selector
Selector selector = Selector.open();
// 将ServerSocketChannel注册到Selector上,并监听连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 阻塞等待事件
selector.select();
// 获取事件和事件通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
// 处理事件
while (it.hasNext()) {
SelectionKey selectionKey = it.next();
it.remove();
if (selectionKey.isAcceptable()) {
// 处理连接事件
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 处理读取事件
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer);
buffer.flip();
String message = Charset.forName("UTF-8").newDecoder().decode(buffer).toString();
System.out.println("读取信息:" + message);
socketChannel.write(ByteBuffer.wrap(("收到信息:" + message).getBytes()));
socketChannel.close();
}
}
}
}
以上代码中,我们首先创建一个ServerSocketChannel
,并将它的端口绑定到我们要监听的端口上。然后,我们将ServerSocketChannel
注册到Selector
中并监听连接事件(SelectionKey.OP_ACCEPT
)。接下来,我们进入一个无限循环,调用Selector
的select()
方法模拟轮询事件。当select()
返回时,我们调用Selector
的selectedKeys()
方法获取到所有发生的事件。然后,我们遍历选择键(Selection Key)集合,并处理每一个事件。当我们监听到一个连接事件时,我们通过ServerSocketChannel
的accept()
方法接受连接,并将其注册到Selector
中监听读事件。当我们监听到读事件时,我们可以通过SocketChannel
的read()
方法读取客户端发送的数据,并在控制台上输出。最后,我们通过SocketChannel
的write()
方法向客户端发送数据,然后关闭连接。
总结
Java NIO提供了一种新的I/O操作方式,相较于传统I/O具有更高的处理效率和更灵活的使用方式,这在某些高并发场景下会提供很大的帮助作用。具体的操作与实现方式还需要在实践中去逐步掌握。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java NIO原理图文分析及代码实现 - Python技术站