什么是NIO?NIO的原理是什么机制?

NIO和IO到底有什么区别?有什么关系?

首先说一下核心区别:

  • NIO是以块的方式处理数据,但是IO是以最基础的字节流的形式去写入和读出的。所以在效率上的话,肯定是NIO效率比IO效率会高出很多。

  • NIO不在是和IO一样用OutputStream和InputStream 输入流的形式来进行处理数据的,但是又是基于这种流的形式,而是采用了通道和缓冲区的形式来进行处理数据的。

  • 还有一点就是NIO的通道是可以双向的,但是IO中的流只能是单向的。

  • 还有就是NIO的缓冲区(其实也就是一个字节数组)还可以进行分片,可以建立只读缓冲区、直接缓冲区和间接缓冲区,只读缓冲区很明显就是字面意思,直接缓冲区是为加快 I/O 速度,而以一种特殊的方式分配其内存的缓冲区。

  • 补充一点:NIO比传统的BIO核心区别就是,NIO采用的是多路复用的IO模型,普通的IO用的是阻塞的IO模型,两个之间的效率肯定是多路复用效率更高

先了解一下什么是通道,什么是缓冲区的概念

通道是个什么意思:

  • 通道是对原 I/O 包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个 Channel 对象(通道)。一个 Buffer 实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。Channel是一个对象,可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较,通道就像是流。

  • 正如前面提到的,所有数据都通过 Buffer 对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。

缓冲区是什么意思:

  • Buffer 是一个对象, 它包含一些要写入或者刚读出的数据。在 NIO 中加入 Buffer 对象,体现了新库与原 I/O 的一个重要区别。在面向流的 I/O 中,您将数据直接写入或者将数据直接读到 Stream 对象中

  • 在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中的数据,您都是将它放到缓冲区中。

  • 缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程

缓冲区的类型:

  • ByteBuffer

  • CharBuffer

  • ShortBuffer

  • IntBuffer

  • LongBuffer

  • FloatBuffer

  • DoubleBuffer

NIO的底层工作原理

先来了解一下buffer的工作机制:

  • capacity 缓冲区数组的总长度

  • position 下一个要操作的数据元素的位置

  • limit 缓冲区数组中不可操作的下一个元素的位置,limit<=capacity

  • mark 用于记录当前 position 的前一个位置或者默认是 0

1.这一步其实是当我们刚开始初始化这个buffer数组的时候,开始默认是这样的

图片

2、但是当你往buffer数组中开始写入的时候几个字节的时候就会变成下面的图,position会移动你数据的结束的下一个位置,这个时候你需要把buffer中的数据写到channel管道中,所以此时我们就需要用这个buffer.flip();方法,

图片

3、当你调用完2中的方法时,这个时候就会变成下面的图了,这样的话其实就可以知道你刚刚写到buffer中的数据是在position---->limit之间,然后下一步调用clear();

图片

4、这时底层操作系统就可以从缓冲区中正确读取这 5 个字节数据发送出去了。在下一次写数据之前我们在调一下 clear() 方法。缓冲区的索引状态又回到初始位置。(其实这一步有点像IO中的把转运字节数组 char[] buf = new char[1024]; 不足1024字节的部分给强制刷新出去的意思)推荐:250期面试题

补充:

1、这里还要说明一下 mark,当我们调用 mark() 时,它将记录当前 position 的前一个位置,当我们调用 reset 时,position 将恢复 mark 记录下来的值

2、clear()方法会:清空整个缓冲区。position将被设回0,limit被设置成 capacity的值(这个个人的理解就是当你在flip()方法的基础上已经记住你写入了多少字节数据,直接把position到limit之间的也就是你写入已经记住的数据给“复制”到管道中)

3、当你把缓冲区的数局写入到管道中的时候,你需要调用flip()方法将Buffer从写模式切换到读模式,调用flip()方法会将position设回0,并将limit设置成之前position的值。buf.flip();(其实我个人理解的就相当于先记住缓冲区缓冲了多少数据)

图片

NIO 工作代码示例

public void selector() throws IOException {
//先给缓冲区申请内存空间
        ByteBuffer buffer = ByteBuffer.allocate(1024);
     //打开Selector为了它可以轮询每个 Channel 的状态
        Selector selector = Selector.open();
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);//设置为非阻塞方式
        ssc.socket().bind(new InetSocketAddress(8080));
        ssc.register(selector, SelectionKey.OP_ACCEPT);//注册监听的事件
        while (true) {
            Set selectedKeys = selector.selectedKeys();//取得所有key集合
            Iterator it = selectedKeys.iterator();
            while (it.hasNext()) {
                SelectionKey key = (SelectionKey) it.next();
                if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
                    ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();
                 SocketChannel sc = ssChannel.accept();//接受到服务端的请求
                    sc.configureBlocking(false);
                    sc.register(selector, SelectionKey.OP_READ);
                    it.remove();
                } else if 
                ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
                    SocketChannel sc = (SocketChannel) key.channel();
                    while (true) {
                        buffer.clear();
                        int n = sc.read(buffer);//读取数据
                        if (n <= 0) {
                            break;
                        }
                        buffer.flip();
                    }
                    it.remove();
                }
            }
        }
}

最后给大家看一下整体的NIO的示意图

图片

NIO和Netty的工作模型对比?

(1)NIO的工作流程步骤:

  • 首先是先创建ServerSocketChannel 对象,和真正处理业务的线程池

  • 然后给刚刚创建的ServerSocketChannel 对象进行绑定一个对应的端口,然后设置为非阻塞

  • 然后创建Selector对象并打开,然后把这Selector对象注册到ServerSocketChannel 中,并设置好监听的事件,监听 SelectionKey.OP_ACCEPT

  • 接着就是Selector对象进行死循环监听每一个Channel通道的事件,循环执行 Selector.select() 方法,轮询就绪的 Channel

  • 从Selector中获取所有的SelectorKey(这个就可以看成是不同的事件),如果SelectorKey是处于 OP_ACCEPT 状态,说明是新的客户端接入,调用 ServerSocketChannel.accept 接收新的客户端。

  • 然后对这个把这个接受的新客户端的Channel通道注册到ServerSocketChannel上,并且把之前的OP_ACCEPT 状态改为SelectionKey.OP_READ读取事件状态,并且设置为非阻塞的,然后把当前的这个SelectorKey给移除掉,说明这个事件完成了

  • 如果第5步的时候过来的事件不是OP_ACCEPT 状态,那就是OP_READ读取数据的事件状态,然后调用本文章的上面的那个读取数据的机制就可以了

(2)Netty的工作流程步骤:

  • 创建 NIO 线程组 EventLoopGroup 和 ServerBootstrap。

  • 设置 ServerBootstrap 的属性:线程组、SO_BACKLOG 选项,设置 NioServerSocketChannel 为 Channel,设置业务处理 Handler

  • 绑定端口,启动服务器程序。

  • 在业务处理 TimeServerHandler 中,读取客户端发送的数据,并给出响应

(3)两者之间的区别:

  • OP_ACCEPT 的处理被简化,因为对于 accept 操作的处理在不同业务上都是一致的。

  • 在 NIO 中需要自己构建 ByteBuffer 从 Channel 中读取数据,而 Netty 中数据是直接读取完成存放在 ByteBuf 中的。相当于省略了用户进程从内核中复制数据的过程。

  • 在 Netty 中,我们看到有使用一个解码器 FixedLengthFrameDecoder,可以用于处理定长消息的问题,能够解决 TCP 粘包读半包问题,十分方便。

<think>好的,我现在需要回答用户关于NIO的定义及其常用模块的问题。用户还提供了三个引用内容,我需要参考这些内容并结合之前的对话来构建回答。首先,我应该明确用户的需求:他们想了解NIO的基本定义以及Java中常用的NIO模块有哪些。 首先,回顾用户提供的引用内容。引用[1]提到NIO是同步非阻塞的I/O模型,用于高并发和大量连接处理。引用[2]区分了BIO、NIO和AIO,指出NIO是非阻塞的。引用[3]以Tomcat的Http11NioProtocol为例,说明NIO如何通过单线程管理多个连接,提高效率。这些信息可以帮助我构建定义部分。 接下来,我需要确定NIO的常用模块。Java NIO的主要组件包括Buffer、Channel、Selector、Charset等。根据之前的回答,用户可能希望了解每个模块的作用和它们之间的关系。例如,Buffer用于数据存储,Channel用于数据传输,Selector用于多路复用,Charset处理字符编码。 然后,我需要考虑如何结构化回答。用户之前的回答有清晰的层次结构,比如分点说明,使用示例代码,以及相关问题。我应该遵循类似的格式,确保回答结构清晰,符合用户的要求。 另外,用户之前的回答中有引用标识,例如[^1]、[^3]。我需要检查提供的引用内容,正确引用相关部分。例如,在定义NIO时,引用[1]和[3]可以作为支持。在提到Selector多路复用时,引用[3]中的Tomcat例子可以说明其实际应用。 还需要注意用户可能有的深层需求。例如,用户可能想知道为什么NIO比BIO高效,或者不同模块如何协同工作。因此,在解释每个模块时,可以简要说明其优势,比如Selector如何减少线程数量,提升性能。 最后,生成相关问题。根据用户之前的模式,问题应围绕NIO的进一步应用、与其他IO模型的对比,以及模块的具体使用场景。例如,询问Selector的工作原理,或者NIO和AIO的区别。 需要确保回答符合系统级指令:使用中文,数学表达式正确,引用适当,生成相关问题。同时避免使用Markdown,保持自然的口语化表达。检查是否有遗漏的模块或错误的信息,确保内容准确可靠。</think>### NIO的定义与核心模块解析 #### 一、NIO的定义 **NIO(Non-blocking I/O)** 是一种同步非阻塞的I/O模型,核心特点是**单线程可管理多个连接**,通过事件驱动机制实现高效资源利用。与传统的阻塞式I/O(BIO)不同,NIO允许线程在数据未就绪时处理其他任务,而非阻塞等待[^1][^3]。 **关键特性**: 1. **非阻塞**:线程不会因I/O操作未完成而阻塞,例如`SocketChannel.configureBlocking(false)`。 2. **多路复用**:通过`Selector`监控多个通道(Channel)的事件(如连接、读、写)。 3. **零拷贝**:支持通过`FileChannel.transferTo()`减少数据在内存中的复制次数。 --- #### 二、Java NIO的常用模块 ##### 1. **Buffer(缓冲区)** - **作用**:临时存储数据,提供结构化访问接口。 - **类型**:`ByteBuffer`、`IntBuffer`、`CharBuffer`等。 - **核心操作**: ```java ByteBuffer buffer = ByteBuffer.allocate(1024); // 分配空间 buffer.put(data); // 写入数据 buffer.flip(); // 切换为读模式 buffer.get(); // 读取数据 buffer.clear(); // 清空缓冲区 ``` ##### 2. **Channel(通道)** - **作用**:连接数据源(如文件、套接字),支持双向数据传输。 - **常见实现**: - `FileChannel`:文件读写。 - `SocketChannel`/`ServerSocketChannel`:TCP网络通信。 - `DatagramChannel`:UDP通信。 - **示例**: ```java // 文件复制(零拷贝) try (FileChannel src = FileChannel.open(Path.of("source.txt")); FileChannel dest = FileChannel.open(Path.of("dest.txt"), StandardOpenOption.CREATE)) { src.transferTo(0, src.size(), dest); } ``` ##### 3. **Selector(选择器)** - **作用**:实现多路复用,监听多个Channel的事件(如连接就绪、可读、可写)。 - **核心流程**: 1. 将Channel注册到Selector,并指定关注的事件。 2. 调用`Selector.select()`阻塞等待事件发生。 3. 通过`SelectionKey`处理就绪的Channel。 - **优势**:单线程可管理数千连接,减少线程切换开销[^3]。 ##### 4. **Charset(字符集)** - **作用**:处理字节与字符的编码/解码。 - **示例**: ```java Charset charset = Charset.forName("UTF-8"); CharBuffer charBuffer = charset.decode(byteBuffer); // 字节转字符 ByteBuffer byteBuffer = charset.encode("Hello"); // 字符转字节 ``` --- #### 三、NIO与BIO的性能对比 | **特性** | **BIO** | **NIO** | |----------------|----------------------------------|----------------------------------| | 线程模型 | 1连接1线程 | 单线程管理多连接 | | 阻塞行为 | 读写操作阻塞线程 | 仅Selector.select()可能阻塞 | | 适用场景 | 低并发、短连接 | 高并发、长连接(如聊天服务器) | | 资源消耗 | 线程数随连接数线性增长 | 固定少量线程 | --- #### 四、实际应用案例 以Tomcat的`Http11NioProtocol`为例: 1. **启动过程**:加载NIO组件,初始化线程池和Selector。 2. **请求处理**:Selector监听所有连接,当请求到达时分配线程处理。单线程可同时管理多个连接的HTTP请求与响应。 --- ### 相关问题 1. **NIO的Selector如何实现多路复用?** 2. **FileChannel的零拷贝技术如何提升文件传输效率?** 3. **NIO和AIO(异步I/O)的核心区别是什么?**
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值