Java NIO(New IO)是Java SE 1.4引入的一个新的IO API,它提供了比传统IO更高效、更灵活的IO操作。与传统IO相比,Java NIO的优势在于它支持非阻塞IO和选择器(Selector)等特性,能够更好地支持高并发、高吞吐量的应用场景。
上图是官方对NIO的说明,Java NIO 官方通常被称为 New I/O(新I/O),但它也因其核心功能非阻塞 I/O 特性而常常被称为 Non-blocking I/O(非阻塞 I/O)。这两个术语在讨论 Java NIO 时都是正确的,它们描述了 Java 中用于处理非阻塞 I/O 操作的机制。
Java IO(Input/Output)是Java语言中用于读写数据的API,它提供了一系列类和接口,用于读取和写入各种类型的数据。下面是Java IO发展史的简要介绍:
NIO 的核心概念是通道 (Channel)、缓冲区 (Buffer) 和选择器 (Selector)。
通道是一个用于读写数据的对象,类似于Java IO中的流(Stream)。与流不同的是,通道可以进行非阻塞式的读写操作,并且可以同时进行读写操作。通道分为两种类型:FileChannel和SocketChannel,分别用于文件和网络通信。
在Java NIO中,所有数据都是通过缓冲区对象进行传输的。缓冲区是一段连续的内存块,可以保存需要读写的数据。缓冲区对象包含了一些状态变量,例如容量(capacity)、限制(limit)、位置(position)等,用于控制数据的读写。
选择器是Java NIO中的一个重要组件,它可以用于同时监控多个通道的读写事件,并在有事件发生时立即做出响应。选择器可以实现单线程监听多个通道的效果,从而提高系统吞吐量和运行效率。
p.s.通常我们看到的图会有thread及client两部分,不太好理解nio主要应用到多个网络通信场景,所以把socketserver-->socketclient画出来就更好理解了。
说到Java NIO大家都会想到上面这张图,NIO应用程序的工作流程如下:
其中
在jdk的安装包里,这个路径JAVA_HOME/sample我们可以找到nio相应的示例代码。
对于简单的文件操作,通常不需要使用选择器。传统的文件I/O操作(如文件读取和写入)可以通过FileChannel等通道进行,但它们不涉及到多路复用,因为文件读写通常是同步的,不需要监视多个通道的状态(如下面demo中的:inChannel.read(byteBuffer)本身还是一个阻塞的方法)。在这种情况下,选择器并不提供额外的好处。
以下是一个简单的Java NIO示例,演示如何从文件中读取数据并打印到控制台:
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOReadFileExample {
public static void main(String[] args) {
// 创建通道,使用 try-with-resources 语句自动关闭资源
try (FileInputStream inputStream = new FileInputStream("source-file-path");
FileChannel inChannel = inputStream.getChannel()) {
// 创建一个缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 使用 while 循环读取文件中的所有数据
while (inChannel.read(byteBuffer) != -1) {
// 切换到读模式
byteBuffer.flip();
// 读取缓冲区中的数据
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
System.out.println(new String(bytes));
// 清空缓冲区
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个示例中,我们首先打开一个文件通道,然后创建一个ByteBuffer来读取数据。我们使用read()方法从文件通道读取数据到缓冲区,然后使用flip()方法切换到读模式,遍历缓冲区并打印数据。最后,我们使用clear()方法清空缓冲区,切换到写模式,以便继续读取数据。最后,我们关闭通道以释放资源。这是一个简单的示例,实际应用中可能需要更多的错误处理和完善。
Java NIO的使用流程通常包括以下步骤:
对于简单的文件操作,通常不需要使用选择器。传统的文件I/O操作(如文件读取和写入)可以通过FileChannel等通道进行,但它们不涉及到多路复用,因为文件读写通常是同步的,不需要监视多个通道的状态。在这种情况下,选择器并不提供额外的好处。
在官方jdk中的exaples中我们可以看到Server有5个子类
Blocking/Single-threaded Server
B1一个阻塞式单线程服务器,在完全服务于一个连接之前不会移动到下一个连接。一个线程来处理所有客户端请求。当等待来自客户端的数据或向客户端写入数据时,服务器将阻塞。这意味着服务器一次只能处理一个客户端请求。
这种类型的服务器简单易于实现,但可扩展性差。这是因为服务器一次只能处理一个客户端请求。如果有许多客户端请求数据,服务器将无法快速响应所有请求。
Blocking/Multi-threaded Server
一个阻塞式多线程服务器,为每个连接创建一个新线程。
服务器将创建多个线程来处理客户端请求。当一个客户端连接到服务器时,服务器使用一个线程来处理该客户端的请求。
这种类型的服务器比阻塞式单线程服务器具有更好的可扩展性,但它仍然不是非常有效率,因为每个连接都需要一个单独的线程。对于大量连接来说,这会导致大量的线程开销。
Blocking/Pooled-thread Server
一个多线程服务器,为服务器使用创建一个线程池。线程池决定如何调度这些线程。服务器将创建一个线程池来处理客户端请求。当一个客户端连接到服务器时,服务器将从线程池中获取一个线程来处理该客户端的请求。当请求处理完毕后,该线程将关闭连接并返回到线程池。
Nonblocking/Single-threaded Server
一个非阻塞式单线程服务器。所有 accept() 和 read()/write() 操作都由一个线程执行,但仅在被 Selector 选中执行这些操作后才执行。服务器将使用 Selector 来监控多个通道的就绪状态。当一个通道就绪时,服务器将从该通道读取数据或向该通道写入数据。
Nonblocking/Dual-threaded Server
一个非阻塞式双线程服务器,在一个线程中执行 accept() 操作,在另一个线程中处理请求。这两个线程都使用 select() 函数。
以下是SocketServer、SocketClient示例代码
SocketServer
import java.io.IOException;
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;
/**
* @Author: Hanko
* @Date: 2023-10-12 17:42
*/
public class SelectorServer {
public static void main(String[] args) throws IOException {
// 创建一个服务器套接字通道
ServerSocketChannel socketChannel = ServerSocketChannel.open();
// 将服务器套接字通道绑定到指定端口
socketChannel.bind(new InetSocketAddress(8888));
// 将服务器套接字通道设置为非阻塞模式
socketChannel.configureBlocking(false);
// 创建一个缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 创建一个选择器
Selector selector = Selector.open();
// 将服务器套接字通道注册到选择器上,监听连接事件
socketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 循环判断通道已准备好进行I/O操作
while (selector.select() > 0) {
// 获取所有发生的SelectionKey
Set selectionKeys = selector.selectedKeys();
// 遍历所有SelectionKey
Iterator iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
// 获取当前SelectionKey
SelectionKey key = iterator.next();
// 判断当前键的通道是否准备好接收socket连接
if (key.isAcceptable()) {
// 接受客户端连接
SocketChannel sc = socketChannel.accept();
// 将客户端连接通道设置为非阻塞模式
sc.configureBlocking(false);
// 将客户端连接通道注册到选择器上,监听读事件
sc.register(selector, SelectionKey.OP_READ);
// 判断当前key的通道是否准备好读取操作
} else if (key.isReadable()) {
// 获取当前key的通道
SocketChannel channel = (SocketChannel) key.channel();
// 从通道中读取数据到缓冲区
int len = 0;
while ((len = channel.read(buffer)) > 0) {
// 将缓冲区切换为读模式
buffer.flip();
// 打印缓冲区中的数据
System.out.println(new String(buffer.array(), 0, len));
// 将缓冲区重置为写模式
buffer.clear();
}
}
// 移除当前事件
iterator.remove();
}
}
}
}
SocketClient
public static void main(String[] args) throws IOException {
// 获取通道、绑定主机和端口
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 8888));
// 切换到非阻塞模式
socketChannel.configureBlocking(false);
// 创建Buffer写入数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 将当前时间写入缓冲区
buffer.put(new Date().toString().getBytes());
// 将缓冲区切换为写模式
buffer.flip();
// 将数据写入通道
socketChannel.write(buffer);
// 关闭通道
socketChannel.close();
}
NIO 适用于以下场景:
这是一个基于java nio实现的高性能、高可靠性的网络框架,它提供了一系列的组件和工具,用于构建异步、事件驱动的网络应用。Netty被广泛应用在互联网、大数据、游戏、通信等领域,一些著名的开源项目如Dubbo、Zookeeper、RocketMQ、Elasticsearch等都基于Netty构建 。
这是一个基于java nio实现的轻量级网络框架,它支持TCP、UDP、SSL等协议,以及多种编解码器和过滤器。Mina可以用于开发高性能的网络服务器和客户端,一些开源项目如Apache Directory Server、Apache James等都使用了Mina 。
这是一个基于java nio实现的Web服务器和Servlet容器,它支持HTTP/2、WebSocket等协议,以及反应式编程模型。Jetty可以嵌入到其他应用中,提供Web服务和Web界面,一些开源项目如Eclipse、Hadoop等都使用了Jetty 。
1、NIO 的优势
NIO 相对于传统 IO 具有以下优势:
2、NIO 的缺点
NIO 相对于传统 IO 具有以下缺点:
为什么NIO没有广泛的被推广起来呢?