“NIO”的版本间的差异

来自姬鸿昌的知识库
跳到导航 跳到搜索
 
(未显示同一用户的8个中间版本)
第2行: 第2行:
  
 
以服务端为例,其实现基本流程如图所示。
 
以服务端为例,其实现基本流程如图所示。
 +
[[文件:Socket 使用流程.png|无|缩略图]]如果客户端还没有对服务端发起连接请求,那么 accept 就会阻塞[阻塞指的是暂停一个线程的执行以等待某个条件发生(例如某资源就绪)]。
 +
 +
如果连接成功,当数据还没有准备好的时候,对 read 的调用同样会阻塞。
 +
 +
当要处理多个连接的时候,就需要采用多线程的方式,由于每个线程都拥有自己的栈空间,而且由于阻塞会导致大量线程进行上下文切换,使得程序的运行效率非常低下。
 +
 +
因此在 J2SE 1.4 中引入了 NIO 来解决这个问题。
 +
 +
 +
NIO 通过 Selector、Channels 和 Buffers 来实现非阻塞的 IO 操作。
 +
 +
NIO 是指 New I/O,既然有 New I/O,那么就会有 Old I/O,Old I/O 是指基于流的 I/O 方法。
 +
 +
NIO 是在 Java 1.4 中被纳入 JDK 中的,它最主要的特点是,提供了基于 Selector 的异步网络 I/O,使得一个线程可以管理多个连接。
 +
 +
基于 NIO 处理多个连接的结构图:
 +
[[文件:NIO 结构图.png|无|缩略图|551x551像素]]在介绍 NIO 的原理之前,首先介绍几个重要的概念:Channel(通道)、Buffer(缓冲区)和 Selector(选择器)。
 +
 +
 +
==== Channel(通道) ====
 +
为了更容易地理解什么是 Channel,这里以 InputStream 为例来介绍什么是 Channel。
 +
 +
传统的 IO 中经常使用下面的代码来读取文件:<syntaxhighlight lang="java">
 +
import java.io.*;
 +
 +
public class NIOTest {
 +
 +
    public static void main(String[] args) throws IOException {
 +
 +
        File file = new File("input.txt");
 +
        InputStream is = new FileInputStream(file);
 +
        byte[] b = new byte[1024];
 +
        int read = 0;
 +
        while ((read = is.read(b)) != -1) {
 +
            //处理读取到的数据
 +
        }
 +
        is.close();
 +
 +
    }
 +
 +
}
 +
</syntaxhighlight>InputStream 其实就是一个用来读取文件的通道。只不过 InputStream 是一个单向的通道,只能用来读取数据。
 +
 +
而 NIO 中的 Channel 是一个双向的通道,不仅能读取数据,而且还能写入数据。
 +
 +
 +
 +
==== Buffer(缓冲区) ====
 +
在上面的示例代码中,InputStream 把读取到的数据放在了 byte 数组中,如果用 OutputStream 写数据,那么也可以把 byte 数组中的数据写到文件中。
 +
 +
而在 NIO 中,数据只能被写到 Buffer 中,同理,读取的数据也只能放在 Buffer 中,由此可见 Buffer 是 Channel 用来读写数据的非常重要的一个工具。
 +
 +
 +
 +
==== Selector(选择器) ====
 +
Selector 是 NIO 中最重要的部分,是实现一个线程管理多个连接的关键,它的作用就是轮询所有被注册的 Channel,一旦发现 Channel 上被注册的事件发生,就可以对这个事件进行处理。
 +
 +
 +
=== Buffer ===
 +
在 Java NIO 中,Buffer 主要的作用就是与 Channel 进行交互。
 +
 +
它本质上是一块可读写数据的内存,这块内存中有很多可以存储 byte、int、char 等的小单元。
 +
 +
这块内存被包装成 NIO Buffer 对象,并提供了一组方法,来简化数据的读写。
 +
 +
在 Java NIO 中,核心的 Buffer 有7类,如图:
 +
[[文件:Buffer 的类图.png|无|缩略图|947x947像素|Buffer 的类图]]
 +
下面重点介绍 Buffer 中几个非常重要的属性:capacity、position 和 limit。
 +
 +
1)capacity 用来表示 Buffer 的容量,也就是刚开始申请的 Buffer 的大小。
 +
 +
2)position 表示下一次读(写)的位置。
 +
 +
在写数据到 Buffer 中时,position 表示当前可写的位置。初始的 position 值为0。
 +
 +
当写入一个数据(例如 int 或 short)到 Buffer 后,position 会向前移动到下一个可插入数据的 Buffer 单元。
 +
 +
position 最大的值为 capacity - 1。
 +
 +
 +
在读取数据时,也是从某个位置开始读。当从 Buffer 的 position 处读取数据完成时,position 也会从向前位置移动到下一个可读的位置。
 +
 +
buffer 从写入模式变为读取模式时,position 会归零,每次读取后,position 向后移动。
 +
 +
 +
3)limit 表示本次读(写)的极限位置。
 +
 +
在写入数据时,limit 表示最多能往 Buffer 里写入多少数据,它等同于 buffer 的容量。
 +
 +
在读取数据时,limit 表示最多能读到多少数据,也就是说 position 移动到 limit 时读操作会停止。它的值等同于写模式下 position 的位置。
 +
 +
为了更容易地理解这三个属性之间的关系,用下图来说明:
 +
[[文件:Buffer 的内部原理.png|无|缩略图|756x756像素]]
 +
在理解了 Buffer 的内部实现原理后,下面重点介绍如何使用 Buffer。
 +
 +
(1)申请 Buffer
 +
 +
在使用 Buffer 前必须先申请一块固定大小的内存空间来供 Buffer 使用,这个工作可以通过 Buffer 类提供的 allocate() 方法来实现。<syntaxhighlight lang="java">
 +
IntBuffer.allocate(64); //申请一个可容纳 64 个 int 的 Buffer
 +
ShortBuffer.allocate(128); //申请一个可容纳 128 个 short 的 Buffer
 +
</syntaxhighlight>

2023年5月9日 (二) 10:20的最新版本

在 NIO(Nonblocking IO,非阻塞 IO)出现之前,Java 是通过传统的 Socket 来实现基本的网络通信功能的。

以服务端为例,其实现基本流程如图所示。

Socket 使用流程.png

如果客户端还没有对服务端发起连接请求,那么 accept 就会阻塞[阻塞指的是暂停一个线程的执行以等待某个条件发生(例如某资源就绪)]。

如果连接成功,当数据还没有准备好的时候,对 read 的调用同样会阻塞。

当要处理多个连接的时候,就需要采用多线程的方式,由于每个线程都拥有自己的栈空间,而且由于阻塞会导致大量线程进行上下文切换,使得程序的运行效率非常低下。

因此在 J2SE 1.4 中引入了 NIO 来解决这个问题。


NIO 通过 Selector、Channels 和 Buffers 来实现非阻塞的 IO 操作。

NIO 是指 New I/O,既然有 New I/O,那么就会有 Old I/O,Old I/O 是指基于流的 I/O 方法。

NIO 是在 Java 1.4 中被纳入 JDK 中的,它最主要的特点是,提供了基于 Selector 的异步网络 I/O,使得一个线程可以管理多个连接。

基于 NIO 处理多个连接的结构图:

生成缩略图出错:无法将缩略图保存到目标地点

在介绍 NIO 的原理之前,首先介绍几个重要的概念:Channel(通道)、Buffer(缓冲区)和 Selector(选择器)。


Channel(通道)

为了更容易地理解什么是 Channel,这里以 InputStream 为例来介绍什么是 Channel。

传统的 IO 中经常使用下面的代码来读取文件:

import java.io.*;

public class NIOTest {

    public static void main(String[] args) throws IOException {

        File file = new File("input.txt");
        InputStream is = new FileInputStream(file);
        byte[] b = new byte[1024];
        int read = 0;
        while ((read = is.read(b)) != -1) {
            //处理读取到的数据
        }
        is.close();

    }

}

InputStream 其实就是一个用来读取文件的通道。只不过 InputStream 是一个单向的通道,只能用来读取数据。

而 NIO 中的 Channel 是一个双向的通道,不仅能读取数据,而且还能写入数据。


Buffer(缓冲区)

在上面的示例代码中,InputStream 把读取到的数据放在了 byte 数组中,如果用 OutputStream 写数据,那么也可以把 byte 数组中的数据写到文件中。

而在 NIO 中,数据只能被写到 Buffer 中,同理,读取的数据也只能放在 Buffer 中,由此可见 Buffer 是 Channel 用来读写数据的非常重要的一个工具。


Selector(选择器)

Selector 是 NIO 中最重要的部分,是实现一个线程管理多个连接的关键,它的作用就是轮询所有被注册的 Channel,一旦发现 Channel 上被注册的事件发生,就可以对这个事件进行处理。


Buffer

在 Java NIO 中,Buffer 主要的作用就是与 Channel 进行交互。

它本质上是一块可读写数据的内存,这块内存中有很多可以存储 byte、int、char 等的小单元。

这块内存被包装成 NIO Buffer 对象,并提供了一组方法,来简化数据的读写。

在 Java NIO 中,核心的 Buffer 有7类,如图:

生成缩略图出错:无法将缩略图保存到目标地点
Buffer 的类图

下面重点介绍 Buffer 中几个非常重要的属性:capacity、position 和 limit。

1)capacity 用来表示 Buffer 的容量,也就是刚开始申请的 Buffer 的大小。

2)position 表示下一次读(写)的位置。

在写数据到 Buffer 中时,position 表示当前可写的位置。初始的 position 值为0。

当写入一个数据(例如 int 或 short)到 Buffer 后,position 会向前移动到下一个可插入数据的 Buffer 单元。

position 最大的值为 capacity - 1。


在读取数据时,也是从某个位置开始读。当从 Buffer 的 position 处读取数据完成时,position 也会从向前位置移动到下一个可读的位置。

buffer 从写入模式变为读取模式时,position 会归零,每次读取后,position 向后移动。


3)limit 表示本次读(写)的极限位置。

在写入数据时,limit 表示最多能往 Buffer 里写入多少数据,它等同于 buffer 的容量。

在读取数据时,limit 表示最多能读到多少数据,也就是说 position 移动到 limit 时读操作会停止。它的值等同于写模式下 position 的位置。

为了更容易地理解这三个属性之间的关系,用下图来说明:

生成缩略图出错:无法将缩略图保存到目标地点

在理解了 Buffer 的内部实现原理后,下面重点介绍如何使用 Buffer。

(1)申请 Buffer

在使用 Buffer 前必须先申请一块固定大小的内存空间来供 Buffer 使用,这个工作可以通过 Buffer 类提供的 allocate() 方法来实现。

IntBuffer.allocate(64); //申请一个可容纳 64 个 int 的 Buffer
ShortBuffer.allocate(128); //申请一个可容纳 128 个 short 的 Buffer