苏州网站建设一条龙,网站开发商城app,虚拟主机WordPress建站,用react做的网站一、NIO简介
Java NIO#xff08;New IO#xff09;是Java SE 1.4引入的一个新的IO API#xff0c;它提供了比传统IO更高效、更灵活的IO操作。与传统IO相比#xff0c;Java NIO的优势在于它支持非阻塞IO和选择器#xff08;Selector#xff09;等特性#xff0c;能够更…一、NIO简介
Java NIONew 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 I/O发展史 Java IOInput/Output是Java语言中用于读写数据的API它提供了一系列类和接口用于读取和写入各种类型的数据。下面是Java IO发展史的简要介绍
JDK 1.01996年 最初的Java IO只支持字节流InputStream、OutputStream和字符流Reader、Writer两种基于阻塞式IOBIO模型。JDK 1.11997年 JDK 1.1引入了NIONew IO包支持了缓存区Buffer、通道Channel等概念提供了更高效的IO操作方式可以实现非阻塞式IONIO模式。JDK 1.42002年 JDK 1.4增加了NIO.2 API也称为Java NIO with buffers提供了更强大的文件处理功能和更高效的IO操作。JDK 72011年 JDK 7引入了NIO.2的改进版——NIO.2 with Completion Ports也称为AIOAsynchronous IO支持异步IO方式在处理大量并发请求时具有优势。
三、NIO 的原理
1、核心概念
NIO 的核心概念是通道 (Channel)、缓冲区 (Buffer) 和选择器 (Selector)。
通道Channel
通道是一个用于读写数据的对象类似于Java IO中的流Stream。与流不同的是通道可以进行非阻塞式的读写操作并且可以同时进行读写操作。通道分为两种类型FileChannel和SocketChannel分别用于文件和网络通信。
缓冲区Buffer
在Java NIO中所有数据都是通过缓冲区对象进行传输的。缓冲区是一段连续的内存块可以保存需要读写的数据。缓冲区对象包含了一些状态变量例如容量capacity、限制limit、位置position等用于控制数据的读写。
选择器Selector
选择器是Java NIO中的一个重要组件它可以用于同时监控多个通道的读写事件并在有事件发生时立即做出响应。选择器可以实现单线程监听多个通道的效果从而提高系统吞吐量和运行效率。
2、原理分解 p.s.通常我们看到的图会有thread及client两部分不太好理解nio主要应用到多个网络通信场景所以把socketserver--socketclient画出来就更好理解了。
说到Java NIO大家都会想到上面这张图NIO应用程序的工作流程如下
创建通道打开一个或多个通道例如FileChannel、SocketChannel等。创建缓冲区为每个通道创建一个或多个缓冲区用于读取或写入数据。注册通道将通道注册到选择器以便选择器可以监控这些通道的状态。选择就绪通道选择器等待通道就绪事件一旦有通道准备好进行I/O操作选择器将通知应用程序。读取/写入数据应用程序从通道读取数据或将数据写入通道使用缓冲区来传输数据。
其中
通道和缓冲区是一对一的关系。每个通道都有一个与之对应的缓冲区用于存储数据。选择器Selector可以同时监视多个通道的状态。一个选择器可以绑定多个通道以实现多路复用。
3、代码示例
在jdk的安装包里这个路径JAVA_HOME/sample我们可以找到nio相应的示例代码。 3.1、简单操作
对于简单的文件操作通常不需要使用选择器。传统的文件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的使用流程通常包括以下步骤
打开通道Channel首先你需要打开一个通道可以是文件通道、套接字通道等。这通常通过FileChannel.open()或SocketChannel.open()等方法实现。创建缓冲区Buffer接下来创建一个或多个缓冲区用于在通道和应用程序之间传输数据。常见的缓冲区包括ByteBuffer、CharBuffer、IntBuffer等。使用ByteBuffer.allocate()或ByteBuffer.allocateDirect()来创建缓冲区。读取/写入数据使用通道的read()方法来从通道读取数据到缓冲区或使用write()方法将数据从缓冲区写入通道。对于套接字通道你可以通过网络发送或接收数据。缓冲区操作对缓冲区进行操作例如读取或写入数据。你可以使用get()方法来获取数据使用put()方法来写入数据。切换缓冲区在读取数据后通常需要切换缓冲区的读模式flip然后开始从缓冲区读取数据。同样在写入数据后切换缓冲区的写模式flip。关闭通道当操作完成后关闭通道以释放资源使用通道的close()方法来实现。 3.2、多个网络通信
对于简单的文件操作通常不需要使用选择器。传统的文件I/O操作如文件读取和写入可以通过FileChannel等通道进行但它们不涉及到多路复用因为文件读写通常是同步的不需要监视多个通道的状态。在这种情况下选择器并不提供额外的好处。
在官方jdk中的exaples中我们可以看到Server有5个子类 B1阻塞式单线程服务器
Blocking/Single-threaded Server
B1一个阻塞式单线程服务器在完全服务于一个连接之前不会移动到下一个连接。一个线程来处理所有客户端请求。当等待来自客户端的数据或向客户端写入数据时服务器将阻塞。这意味着服务器一次只能处理一个客户端请求。
这种类型的服务器简单易于实现但可扩展性差。这是因为服务器一次只能处理一个客户端请求。如果有许多客户端请求数据服务器将无法快速响应所有请求。
BN阻塞式多线程服务器
Blocking/Multi-threaded Server
一个阻塞式多线程服务器为每个连接创建一个新线程。
服务器将创建多个线程来处理客户端请求。当一个客户端连接到服务器时服务器使用一个线程来处理该客户端的请求。
这种类型的服务器比阻塞式单线程服务器具有更好的可扩展性但它仍然不是非常有效率因为每个连接都需要一个单独的线程。对于大量连接来说这会导致大量的线程开销。
BP阻塞式线程池服务器
Blocking/Pooled-thread Server 一个多线程服务器为服务器使用创建一个线程池。线程池决定如何调度这些线程。服务器将创建一个线程池来处理客户端请求。当一个客户端连接到服务器时服务器将从线程池中获取一个线程来处理该客户端的请求。当请求处理完毕后该线程将关闭连接并返回到线程池。
N1非阻塞式单线程服务器
Nonblocking/Single-threaded Server
一个非阻塞式单线程服务器。所有 accept() 和 read()/write() 操作都由一个线程执行但仅在被 Selector 选中执行这些操作后才执行。服务器将使用 Selector 来监控多个通道的就绪状态。当一个通道就绪时服务器将从该通道读取数据或向该通道写入数据。
N2非阻塞式双线程服务器
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) {// 获取所有发生的SelectionKeySetSelectionKey selectionKeys selector.selectedKeys();// 遍历所有SelectionKeyIteratorSelectionKey iterator selectionKeys.iterator();while (iterator.hasNext()) {// 获取当前SelectionKeySelectionKey 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 的应用场景
NIO 适用于以下场景
网络通信NIO 可以用于开发高并发的网络应用例如 Web 服务器、游戏服务器等。文件操作NIO 可以用于开发高性能的文件操作应用例如文件传输、文件压缩等。进程间通信NIO 可以用于实现进程间通信例如共享内存、管道等。数据库操作NIO 可以用于提高数据库操作的性能例如批量插入、批量查询等。
1、在业务中的应用
聊天服务器使用 NIO 来建立和维护多个客户端连接并高效地处理客户端请求。文件传输使用 NIO 来高效地传输大文件。数据库操作使用 NIO 来批量插入或查询数据提高数据库操作的性能。
2、在框架中的应用
Netty 这是一个基于java nio实现的高性能、高可靠性的网络框架它提供了一系列的组件和工具用于构建异步、事件驱动的网络应用。Netty被广泛应用在互联网、大数据、游戏、通信等领域一些著名的开源项目如Dubbo、Zookeeper、RocketMQ、Elasticsearch等都基于Netty构建 。
Mina 这是一个基于java nio实现的轻量级网络框架它支持TCP、UDP、SSL等协议以及多种编解码器和过滤器。Mina可以用于开发高性能的网络服务器和客户端一些开源项目如Apache Directory Server、Apache James等都使用了Mina 。
Jetty 这是一个基于java nio实现的Web服务器和Servlet容器它支持HTTP/2、WebSocket等协议以及反应式编程模型。Jetty可以嵌入到其他应用中提供Web服务和Web界面一些开源项目如Eclipse、Hadoop等都使用了Jetty 。 五、优缺点
1、NIO 的优势
NIO 相对于传统 IO 具有以下优势
提高并发性NIO 可以使用多路复用器来监听多个通道的事件提高并发性。提高性能NIO 支持非阻塞 IO可以提高性能。简化编程NIO 的 API 更加简洁易于理解和使用。
2、NIO 的缺点
NIO 相对于传统 IO 具有以下缺点
学习成本较高NIO 的概念和 API 与传统 IO 不同学习成本较高。不兼容性NIO 与传统 IO 存在不兼容性需要注意兼容性问题。 为什么NIO没有广泛的被推广起来呢
复杂性相对于传统的阻塞式I/OJava NIO 的编程模型更加复杂。它需要开发人员处理事件、缓冲区管理、选择器等概念这可能会增加学习曲线尤其是对于新手来说。性能优势局限Java NIO 在高并发和高吞吐量的场景下可以提供性能优势但对于许多常规应用程序而言传统的阻塞式I/O 已经足够了。只有需要处理大量并发连接或需要高度定制化的网络通信时Java NIO 才会显得更有价值。第三方库的竞争有一些第三方库和框架如Netty和Apache MINA构建在Java NIO 之上提供了更易于使用的高性能网络通信解决方案。这些库可能更容易推广而不是直接使用Java NIO。历史原因许多早期的Java应用程序是基于传统的阻塞式I/O构建的而且迁移到Java NIO 可能需要重写或修改现有的代码。这使得许多遗留应用程序不愿意切换到新的I/O模型。