diff --git a/README.md b/README.md index 211700d2b7b08d9a1d9c8dc3f970b220f517fa8c..986db54d24cf64a54d0714625bdfe91f8e138c6d 100644 --- a/README.md +++ b/README.md @@ -218,8 +218,11 @@ ## Java NIO +- [为什么我们要使用 Java NIO?](docs/nio/why.md) +- [Java NIO 快速入门(buffer缓冲区、Channel管道、Selector选择器)](docs/nio/rumen.md) +- [一文彻底理解Java IO模型(阻塞IO非阻塞IO/IO多路复用)](docs/nio/moxing.md) +- [使用Java NIO完成网络通信](docs/nio/network-connect.md) - [如何给女朋友解释什么是 BIO、NIO 和 AIO?](docs/nio/BIONIOAIO.md) -- [一文彻底理解 Java NIO 核心组件](docs/nio/nio.md) ## Java并发编程 diff --git a/docs/.vuepress/sidebar.ts b/docs/.vuepress/sidebar.ts index aa9149ddb477ae5259fb030c19d44c5a4df67c74..357986730e443af5e6a20c07bd612c4e145a39dd 100644 --- a/docs/.vuepress/sidebar.ts +++ b/docs/.vuepress/sidebar.ts @@ -330,8 +330,11 @@ export const sidebarConfig = sidebar({ collapsable: true, prefix: "nio/", children: [ + "why", + "rumen", + "moxing", + "network-connect", "BIONIOAIO", - "nio", ], }, { diff --git a/docs/home.md b/docs/home.md index e045b7c991bdbae13526da5a1aa9b41933fba94c..91550f3e8a03370350d59ee73225dcede950d02d 100644 --- a/docs/home.md +++ b/docs/home.md @@ -233,8 +233,11 @@ head: ### Java NIO +- [为什么我们要使用 Java NIO?](nio/why.md) +- [Java NIO 快速入门(buffer缓冲区、Channel管道、Selector选择器)](nio/rumen.md) +- [一文彻底理解Java IO模型(阻塞IO非阻塞IO/IO多路复用)](nio/moxing.md) +- [使用Java NIO完成网络通信](nio/network-connect.md) - [如何给女朋友解释什么是 BIO、NIO 和 AIO?](nio/BIONIOAIO.md) -- [一文彻底理解 Java NIO 核心组件](nio/nio.md) ### Java并发编程 diff --git a/docs/nice-article/cnblog/javanioxxbjy.md b/docs/nice-article/cnblog/javanioxxbjy.md deleted file mode 100644 index e4118438774fc0f0ce8bcb0762ba46fe47003d8f..0000000000000000000000000000000000000000 --- a/docs/nice-article/cnblog/javanioxxbjy.md +++ /dev/null @@ -1,290 +0,0 @@ ---- -title: Java NIO 学习笔记(一) -shortTitle: Java NIO 学习笔记(一) -author: 概述,Channel/Buffer -category: - - 博客园 ---- - -**目录:** - -[Java NIO 学习笔记(一)----概述,Channel/Buffer](https://www.cnblogs.com/czwbig/p/10035631.html) - -[Java NIO 学习笔记(二)----聚集和分散,通道到通道](https://www.cnblogs.com/czwbig/p/10040349.html) - -[Java NIO 学习笔记(三)----Selector](https://www.cnblogs.com/czwbig/p/10043421.html) - -[Java NIO 学习笔记(四)----文件通道和网络通道](https://www.cnblogs.com/czwbig/p/10046987.html) - -[Java NIO 学习笔记(五)----路径、文件和管道 Path/Files/Pipe](https://www.cnblogs.com/czwbig/p/10056126.html) - -[Java NIO 学习笔记(六)----异步文件通道 AsynchronousFileChannel](https://www.cnblogs.com/czwbig/p/10056131.html) - -[Java NIO 学习笔记(七)----NIO/IO 的对比和总结](https://www.cnblogs.com/czwbig/p/10056804.html) - -Java NIO (来自 Java 1.4)可以替代标准 IO 和 Java Networking API ,NIO 提供了与标准 IO 不同的使用方式。学习 NIO 之前建议先掌握标准 IO 和 Java 网络编程,推荐教程: - -* [系统学习 Java IO----目录,概览](https://www.cnblogs.com/czwbig/p/10007201.html) -* [初步接触 Java Net 网络编程](https://www.cnblogs.com/czwbig/p/10018118.html) - -**本文目的:** 掌握了标准 IO 之后继续学习 NIO 知识。主要参考 JavaDoc 和 Jakob Jenkov 的英文教程 [Java NIO Tutorial](http://tutorials.jenkov.com/java-nio/index.html) - -# Java NIO 概览 - -NIO 由以下核心组件组成: - -1. 通道和缓冲区 - -在标准 IO API 中,使用字节流和字符流。 在 NIO 中使用通道和缓冲区。 数据总是从通道读入缓冲区,或从缓冲区写入通道。 -2. 非阻塞IO - -NIO 可以执行非阻塞 IO 。 例如,当通道将数据读入缓冲区时,线程可以执行其他操作。 并且一旦数据被读入缓冲区,线程就可以继续处理它。 将数据写入通道也是如此。 -3. 选择器 - -NIO 包含“选择器”的概念。 选择器是一个可以监视多个事件通道的对象(例如:连接打开,数据到达等)。 因此,单个线程可以监视多个通道的数据。 - -NIO 有比这些更多的类和组件,但在我看来,Channel,Buffer 和 Selector 构成了 API 的核心。 其余的组件,如 Pipe 和 FileLock ,只是与三个核心组件一起使用的实用程序类。 - -### Channels/Buffers 通道和缓冲区 - -通常,NIO 中的所有 IO 都以 Channel 开头,频道有点像流。 数据可以从 Channel 读入 Buffer,也可以从 Buffer 写入 Channel : - -![通道将数据读入缓冲区,缓冲区将数据写入通道](//upload-images.jianshu.io/upload_images/14923529-b3432ef114d32991.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -有几种 Channel 和 Buffer ,以下是 NIO 中主要 Channel 实现类的列表,这些通道包括 UDP + TCP 网络 IO 和文件 IO: - -* FileChannel :文件通道 -* DatagramChannel :数据报通道 -* SocketChannel :套接字通道 -* ServerSocketChannel :服务器套接字通道 - -这些类也有一些有趣的接口,但为了简单起见,这里暂时不提,后续会进行学习的。 - -以下是 NIO 中的核心 Buffer 实现,其实就是 7 种基本类型: - -* ByteBuffer -* CharBuffer -* ShortBuffer -* IntBuffer -* LongBuffer -* FloatBuffer -* DoubleBuffer - -NIO 还有一个 MappedByteBuffer,它与内存映射文件一起使用,同样这个后续再讲。 - -### Selectors 选择器 - -选择器允许单个线程处理多个通道。 如果程序打开了许多连接(通道),但每个连接只有较低的流量,使用选择器就很方便。 例如,在聊天服务器中, 以下是使用 Selector 处理 3 个 Channel 的线程图示: - -![1个线程使用选择器处理3个通道](//upload-images.jianshu.io/upload_images/14923529-6c435a8b1a6f1593.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -要使用选择器,需要使用它注册通道。 然后你调用它的 select() 方法。 此方法将阻塞,直到有一个已注册通道的事件准备就绪。 一旦该方法返回,该线程就可以处理这些事件。 事件可以是传入连接,接收数据等。 - -# Channel (通道) - -NIO 通道类似于流,但有一些区别: - -* 通道可以读取和写入。 流通常是单向的(读或写)。 -* 通道可以异步读取和写入。 -* 通道始终读取或写入缓冲区,即它只面向缓冲区。 - -如上所述,NIO 中总是将数据从通道读取到缓冲区,或将数据从缓冲区写入通道。 这是一个例子: - -```java -// 文件内容是 123456789 -RandomAccessFile accessFile = new RandomAccessFile("D:\\test\\1.txt", "rw"); -FileChannel fileChannel = accessFile.getChannel(); - -ByteBuffer buffer = ByteBuffer.allocate(48); - -int data = fileChannel.read(buffer); // 将 Channel 的数据读入缓冲区,返回读入到缓冲区的字节数 -``` - - -# Buffer(缓冲区) - -使用 Buffer 与 Channel 交互,数据从通道读入缓冲区,或从缓冲区写入通道。 - -缓冲区本质上是一个可以写入数据的内存块,之后可以读取数据。 Buffer 对象包装了此内存块,提供了一组方法,可以更轻松地使用内存块。 - -### Buffer 的基本用法 - -使用 Buffer 读取和写入数据通常遵循以下四个步骤: - -1. 将数据写入缓冲区 -2. 调用 buffer.flip() 反转读写模式 -3. 从缓冲区读取数据 -4. 调用 buffer.clear() 或 buffer.compact() 清除缓冲区内容 - -将数据写入Buffer 时,Buffer 会跟踪写入的数据量。 当需要读取数据时,就使用 flip() 方法将缓冲区从写入模式切换到读取模式。 在读取模式下,缓冲区允许读取写入缓冲区的所有数据。 - -读完所有数据之后,就需要清除缓冲区,以便再次写入。 可以通过两种方式执行此操作:通过调用 clear() 或调用 compact() 。区别在于 clear() 是方法清除整个缓冲区,而 compact() 方法仅清除已读取的数据,未读数据都会移动到缓冲区的开头,新数据将在未读数据之后写入缓冲区。 - -这是一个简单的缓冲区用法示例: - -```java -public class ChannelExample { - public static void main(String[] args) throws IOException { - // 文件内容是 123456789 - RandomAccessFile accessFile = new RandomAccessFile("D:\\test\\1.txt", "rw"); - FileChannel fileChannel = accessFile.getChannel(); - - ByteBuffer buffer = ByteBuffer.allocate(48); //创建容量为48字节的缓冲区 - - int data = fileChannel.read(buffer); // 将 Channel 的数据读入缓冲区,返回读入到缓冲区的字节数 - while (data != -1) { - System.out.println("Read " + data); // Read 9 - buffer.flip(); // 将 buffer 从写入模式切换为读取模式 - while (buffer.hasRemaining()) { - System.out.print((char) buffer.get()); // 每次读取1byte,循环输出 123456789 - } - buffer.clear(); // 清除当前缓冲区 - data = fileChannel.read(buffer); // 将 Channel 的数据读入缓冲区 - } - accessFile.close(); - } -} -``` - - -##### Buffer 的 capacity,position 和 limit - -缓冲区有 3 个需要熟悉的属性,以便了解缓冲区的工作原理。 这些是: - -1. capacity : 容量缓冲区的容量,是它所包含的元素的数量。不能为负并且不能更改。 -2. position :缓冲区的位置 是下一个要读取或写入的元素的索引。不能为负,并且不能大于 limit -3. limit : 缓冲区的限制,缓冲区的限制不能为负,并且不能大于 capacity - -另外还有标记 mark , - -标记、位置、限制和容量值遵守以下不变式: - -0 <= mark<= position <= limit<= capacity - -position 和 limit 的含义取决于 Buffer 是处于读取还是写入模式。 无论缓冲模式如何,capacity 总是一样的表示容量。 - -以下是写入和读取模式下的容量,位置和限制的说明: - -![](//upload-images.jianshu.io/upload_images/14923529-02664119749dc674.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) - -##### capacity - -作为存储器块,缓冲区具有一定的固定大小,也称为“容量”。 只能将 capacity 多的 byte,long,char 等写入缓冲区。 缓冲区已满后,需要清空它(读取数据或清除它),然后才能将更多数据写入。 - -##### position - -将数据写入缓冲区时,可以在某个位置执行操作。 position 初始值为 0 ,当一个 byte,long,char 等已写入缓冲区时,position 被移动,指向缓冲区中的下一个单元以插入数据。 position 最大值为 capacity -1 - -从缓冲区读取数据时,也可以从给定位置开始读取数据。 当缓冲区从写入模式切换到读取模式时,position 将重置为 0 。当从缓冲区读取数据时,将从 position 位置开始读取数据,读取后会将 position 移动到下一个要读取的位置。 - -##### limit - -在写入模式下,Buffer 的 limit 是可以写入缓冲区的数据量的限制,此时 limit=capacity。 - -将缓冲区切换为读取模式时,limit 表示最多能读到多少数据。 因此,当将 Buffer 切换到读取模式时,limit被设置为之前写入模式的写入位置(position ),换句话说,你能读到之前写入的所有数据(例如之前写写入了 6 个字节,此时 position=6 ,然后切换到读取模式,limit 代表最多能读取的字节数,因此 limit 也等于 6)。 - -##### 分配缓冲区 - -要获取 Buffer 对象,必须先分配它。 每个 Buffer 类都有一个 allocate() 方法来执行此操作。 下面是一个显示ByteBuffer分配的示例,容量为48字节: - -```java -ByteBuffer buffer = ByteBuffer.allocate(48); //创建容量为48字节的缓冲区 -``` - - -##### 将数据写入缓冲区 - -可以通过两种方式将数据写入 Buffer: - -1. 将数据从通道写入缓冲区 -2. 通过缓冲区的 put() 方法,自己将数据写入缓冲区。 - -这是一个示例,显示了 Channel 如何将数据写入 Buffer: - -```java -int data = fileChannel.read(buffer); // 将 Channel 的数据读入缓冲区,返回读入到缓冲区的字节数 -buffer.put(127); // 此处的 127 是 byte 类型 -``` - - -put() 方法有许多其他版本,允许以多种不同方式将数据写入 Buffer 。 例如,在特定位置写入,或将一个字节数组写入缓冲区。 - -##### flip() 切换缓冲区的读写模式 - -flip() 方法将 Buffer 从写入模式切换到读取模式。 调用 flip() 会将 position 设置回 0,并将 limit 的值设置为切换之前的 position 值。换句话说,limit 表示之前写进了多少个 byte、char 等 —— 现在能读取多少个 byte、char 等。 - -##### 从缓冲区读取数据 - -有两种方法可以从 Buffer 中读取数据: - -1. 将数据从缓冲区读入通道。 -2. 使用 get() 方法之一,自己从缓冲区读取数据。 - -以下是将缓冲区中的数据读入通道的示例: - -```java -int bytesWritten = fileChannel.write(buffer); -byte aByte = buffer.get(); -``` - - -和 put() 方法一样,get() 方法也有许多其他版本,允许以多种不同方式从 Buffer 中读取数据。有关更多详细信息,请参阅JavaDoc以获取具体的缓冲区实现。 - -以下列出 ByteBuffer 类的部分方法: - -方法|描述| ----|---| -byte\[\] array()|返回实现此缓冲区的 byte 数组,此缓冲区的内容修改将导致返回的数组内容修改,反之亦然。| -CharBuffer asCharBuffer()|创建此字节缓冲区作为新的独立的char 缓冲区。新缓冲区的内容将从此缓冲区的当前位置开始| -XxxBuffer asXxxBuffer()|同上,创建对应的 Xxx 缓冲区,Xxx 可为 Short/Int/Long/Float/Double| -byte get()|相对 get 方法。读取此缓冲区当前位置的字节,然后该 position 递增。| -ByteBuffer get(byte\[\] dst, int offset, int length)|相对批量 get 方法,后2个参数可省略| -byte get(int index)|绝对 get 方法。读取指定索引处的字节。| -char getChar()|用于读取 char 值的相对 get 方法。| -char getChar(int index)|用于读取 char 值的绝对 get 方法。| -xxx getXxx(int index)|用于读取 xxx 值的绝对 get 方法。index 可以选,指定位置。| -众多 put() 方法|参考以上 get() 方法| -static ByteBuffer wrap(byte\[\] array)|将 byte 数组包装到缓冲区中。| - -##### rewind() 倒带 - -Buffer对象的 rewind() 方法将 position 设置回 0,因此可以重读缓冲区中的所有数据, limit 则保持不变。 - -##### clear() 和 compact() - -如果调用 clear() ,则将 position 设置回 0 ,并将 limit 被设置成 capacity 的值。换句话说,Buffer 被清空了。 但是 Buffer 中的实际存放的数据并未清除。 - -如果在调用 clear() 时缓冲区中有任何未读数据,数据将被“遗忘”,这意味着不再有任何标记告诉读取了哪些数据,还没有读取哪些数据。 - -如果缓冲区中仍有未读数据,并且想稍后读取它,但需要先写入一些数据,这时候应该调用 compact() ,它会将所有未读数据复制到 Buffer 的开头,然后它将 position 设置在最后一个未读元素之后。 limit 属性仍设置为 capacity ,就像 clear() 一样。 现在缓冲区已准备好写入,并且不会覆盖未读数据。 - -##### mark() 和 reset() - -以通过调用 Buffer 对象的 mark() 方法在 Buffer 中标记给定位置。 然后,可以通过调用 Buffer.reset() 方法将位置重置回标记位置,就像在标准 IO 中一样。 - -```java -buffer.mark(); -// 调用 buffer.get() 等方法读取数据... - -buffer.reset(); // 设置 position 回到 mark 位置。 -``` - - -##### equals() 和 compareTo() - -可以使用 equals() 和 compareTo() 比较两个缓冲区。 - -equals() 成立的条件: - -1. 它们的类型相同(byte,char,int等) -2. 它们在缓冲区中具有相同数量的剩余字节,字符等。 -3. 所有剩余的字节,字符等都相等。 - -如上,equals 仅比较缓冲区的一部分,而不是它内部的每个元素。 实际上,它只是比较缓冲区中的其余元素。 - -compareTo() 方法比较两个缓冲区的剩余元素(字节,字符等), 在下列情况下,一个 Buffer 被视为“小于”另一个 Buffer: - -1. 第一个不相等的元素小于另一个 Buffer 中对应的元素 。 -2. 所有元素都相等,但第一个 Buffer 在第二个 Buffer 之前耗尽了元素(第一个 Buffer 元素较少)。 - ->参考链接:[https://www.cnblogs.com/czwbig/p/10035631.html](https://www.cnblogs.com/czwbig/p/10035631.html),整理:沉默王二 diff --git a/docs/nice-article/other/zhisxqsdljtgfsyygpzsqzsbxdgj.md b/docs/nice-article/other/zhisxqsdljtgfsyygpzsqzsbxdgj.md deleted file mode 100644 index 987e41f4707b0c8ce3503c73a89823434a037532..0000000000000000000000000000000000000000 --- a/docs/nice-article/other/zhisxqsdljtgfsyygpzsqzsbxdgj.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: 知识星球 | 深度连接铁杆粉丝,运营高品质社群,知识变现的工具 -shortTitle: 知识星球 | 深度连接铁杆粉丝,运营高品质社群,知识变现的工具 -description: 知识星球是创作者连接铁杆粉丝,实现知识变现的工具。任何从事创作或艺术的人,例如艺术家、工匠、教师、学术研究、科普等,只要能获得一千位铁杆粉丝,就足够生计无忧,自由创作。社群管理、内容沉淀、链接粉丝等就在知识星球。 -tags: - - 优质文章 -category: - - 其他网站 -head: - - - meta - - name: keywords - content: 社群服务,社群工具,公众号粉丝管理,粉丝社区,内容付费,知识变现,流量变现,粉丝经济,KOL,大 V,知识管理,内容沉淀,企业社区,内部论坛,小团队共享,私密圈,原名小密圈,小秘圈,小蜜圈 ---- - - - ->参考链接:[https://articles.zsxq.com/id_a20wm4o4aawc.html](https://articles.zsxq.com/id_a20wm4o4aawc.html),整理:沉默王二 diff --git a/docs/nio/BIONIOAIO.md b/docs/nio/BIONIOAIO.md index 97e9dc6f656f723e953f057a987f975a700f283a..d32c9a7e3cd03baedf0f440a884bb921a339f803 100644 --- a/docs/nio/BIONIOAIO.md +++ b/docs/nio/BIONIOAIO.md @@ -9,7 +9,7 @@ description: Java程序员进阶之路,小白的零基础Java教程,BIO、NI head: - - meta - name: keywords - content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,Java IO,java BIO,java NIO,java AIO,bio,nio,aio + content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,IO,BIO,NIO,AIO --- diff --git a/docs/nio/moxing.md b/docs/nio/moxing.md new file mode 100644 index 0000000000000000000000000000000000000000..179e31ae1486af0f4cc5e0df2f743d9197250cf0 --- /dev/null +++ b/docs/nio/moxing.md @@ -0,0 +1,156 @@ +--- +title: 一文彻底理解Java IO模型(阻塞IO非阻塞IO/IO多路复用) +shortTitle: 一文彻底理解Java IO模型 +category: + - Java核心 +tag: + - Java NIO +description: Java程序员进阶之路,小白的零基础Java教程,一文彻底理解 Java IO 模型(非阻塞 IO/IO多路复用/异步IO) +head: + - - meta + - name: keywords + content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,nio,多路复用,阻塞IO +--- + + +**文件的IO就告一段落了**,我们来学习网络中的IO~~~为了更好地理解NIO,**我们先来学习一下IO的模型**~ + +根据UNIX网络编程对I/O模型的分类,**在UNIX可以归纳成5种I/O模型**: + +* **阻塞I/O** +* **非阻塞I/O** +* **I/O多路复用** +* 信号驱动I/O +* 异步I/O + +## 学习I/O模型需要的基础 + +### 文件描述符 + +Linux 的内核将所有外部设备**都看做一个文件来操作**,对一个文件的读写操作会**调用内核提供的系统命令(api)**,返回一个`file descriptor`(fd,文件描述符)。而对一个socket的读写也会有响应的描述符,称为`socket fd`(socket文件描述符),描述符就是一个数字,**指向内核中的一个结构体**(文件路径,数据区等一些属性)。 + +* 所以说:在Linux下对文件的操作是**利用文件描述符(file descriptor)来实现的**。 + +### 用户空间和内核空间 + +为了保证用户进程不能直接操作内核(kernel),**保证内核的安全**,操心系统将虚拟空间划分为两部分 + +* **一部分为内核空间**。 +* **一部分为用户空间**。 + +### I/O运行过程 + +我们来看看IO在系统中的运行是怎么样的(我们**以read为例**) + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/moxing-54ee4738-b689-4026-863f-13e456b374de.jpg) + + + +可以发现的是:当应用程序调用read方法时,是需要**等待**的--->从内核空间中找数据,再将内核空间的数据拷贝到用户空间的。 + +* **这个等待是必要的过程**! + +下面只讲解用得最多的3个I/0模型: + +* **阻塞I/O** +* **非阻塞I/O** +* **I/O多路复用** + +## 阻塞I/O模型 + +在进程(用户)空间中调用`recvfrom`,其系统调用直到数据包到达且**被复制到应用进程的缓冲区中或者发生错误时才返回**,在此期间**一直等待**。 + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/moxing-8a1cb207-6c56-4bd8-8489-c21d5a76e1ca.jpg) + + + +## 非阻塞I/O模型 + +`recvfrom`从应用层到内核的时候,如果没有数据就**直接返回**一个EWOULDBLOCK错误,一般都对非阻塞I/O模型**进行轮询检查这个状态**,看内核是不是有数据到来。 + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/moxing-6590a3de-0e7c-4ce2-aa1c-815625095e62.jpg) + + + +## I/O复用模型 + +前面也已经说了:在Linux下对文件的操作是**利用文件描述符(file descriptor)来实现的**。 + +在Linux下它是这样子实现I/O复用模型的: + +* 调用`select/poll/epoll/pselect`其中一个函数,**传入多个文件描述符**,如果有一个文件描述符**就绪,则返回**,否则阻塞直到超时。 + +比如`poll()`函数是这样子的:`int poll(struct pollfd *fds,nfds_t nfds, int timeout);` + +其中 `pollfd` 结构定义如下: + +```c +struct pollfd { + int fd; /* 文件描述符 */ + short events; /* 等待的事件 */ + short revents; /* 实际发生了的事件 */ +}; +``` + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/moxing-aec90e84-33c5-4f5b-997e-8db54d6bce88.jpg) + + + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/moxing-62def8ad-3ca3-467b-81f6-5d0a31dd7fdc.jpg) + + + +* (1)当用户进程调用了select,那么整个进程会被block; +* (2)而同时,kernel会“监视”所有select负责的socket; +* (3)当任何一个socket中的数据准备好了,select就会返回; +* (4)这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程(空间)。 +* 所以,I/O 多路复用的特点是**通过一种机制一个进程能同时等待多个文件描述符**,而这些文件描述符**其中的任意一个进入读就绪状态**,select()函数**就可以返回**。 + +select/epoll的优势并不是对于单个连接能处理得更快,而是**在于能处理更多的连接**。 + +## I/O模型总结 + +正经的描述都在上面给出了,不知道大家理解了没有。下面我举几个例子总结一下这三种模型: + +**阻塞I/O:** + +* Java3y跟女朋友去买喜茶,排了很久的队终于可以点饮料了。我要绿研,谢谢。可是喜茶不是点了单就能立即拿,于是我**在喜茶门口等了一小时才拿到**绿研。 + +* 在门口干等一小时 + + + +**非阻塞I/O:** + +* Java3y跟女朋友去买一点点,排了很久的队终于可以点饮料了。我要波霸奶茶,谢谢。可是一点点不是点了单就能立即拿,**同时**服务员告诉我:你大概要等半小时哦。你们先去逛逛吧~于是Java3y跟女朋友去玩了几把斗地主,感觉时间差不多了。于是**又去一点点问**:请问到我了吗?我的单号是xxx。服务员告诉Java3y:还没到呢,现在的单号是XXX,你还要等一会,可以去附近耍耍。问了好几次后,终于拿到我的波霸奶茶了。 + +* 去逛了下街、斗了下地主,时不时问问到我了没有 + + + +**I/O复用模型:** + +* Java3y跟女朋友去麦当劳吃汉堡包,现在就厉害了可以使用微信小程序点餐了。于是跟女朋友找了个地方坐下就用小程序点餐了。点餐了之后玩玩斗地主、聊聊天什么的。**时不时听到广播在复述XXX请取餐**,反正我的单号还没到,就继续玩呗。~~**等听到广播的时候再取餐就是了**。时间过得挺快的,此时传来:Java3y请过来取餐。于是我就能拿到我的麦辣鸡翅汉堡了。 + +* 听广播取餐,**广播不是为我一个人服务**。广播喊到我了,我过去取就Ok了。 + +>参考链接:[https://www.zhihu.com/question/29005375/answer/667616386](https://www.zhihu.com/question/29005375/answer/667616386),整理:沉默王二 + +--------- + +最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html) + +关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。 + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png) \ No newline at end of file diff --git a/docs/nio/network-connect.md b/docs/nio/network-connect.md new file mode 100644 index 0000000000000000000000000000000000000000..6885d97b9b3943650da63024ca941e6cfba7eba1 --- /dev/null +++ b/docs/nio/network-connect.md @@ -0,0 +1,428 @@ +--- +title: 使用Java NIO完成网络通信 +shortTitle: 使用Java NIO完成网络通信 +category: + - Java核心 +tag: + - Java NIO +description: Java程序员进阶之路,小白的零基础Java教程,使用Java NIO完成网络通信 +head: + - - meta + - name: keywords + content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,nio,网络通信 +--- + +## NIO基础继续讲解 + +回到我们最开始的图: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/network-connect-bb1bd676-8aeb-4428-9498-230a05ee717d.jpg) + + + +NIO被叫为 `no-blocking io`,其实是在**网络这个层次中理解的**,对于**FileChannel来说一样是阻塞**。 + +我们前面也仅仅讲解了FileChannel,对于我们网络通信是还有几个Channel的~ + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/network-connect-ba836b5c-82d2-42b0-b1ae-83f8ee0b0101.jpg) + + + +所以说:我们**通常**使用NIO是在网络中使用的,网上大部分讨论NIO都是在**网络通信的基础之上**的!说NIO是非阻塞的NIO也是**网络中体现**的! + +从上面的图我们可以发现还有一个`Selector`选择器这么一个东东。从一开始我们就说过了,nio的**核心要素**有: + +* Buffer缓冲区 +* Channel通道 +* Selector选择器 + +我们在网络中使用NIO往往是I/O模型的**多路复用模型**! + +* Selector选择器就可以比喻成麦当劳的**广播**。 +* **一个线程能够管理多个Channel的状态** + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/network-connect-63f45193-8eb7-4cc5-a5a7-70713fac0d73.jpg) + + + +## NIO阻塞形态 + +为了更好地理解,我们先来写一下NIO**在网络中是阻塞的状态代码**,随后看看非阻塞是怎么写的就更容易理解了。 + +* **是阻塞的就没有Selector选择器了**,就直接使用Channel和Buffer就完事了。 + +客户端: + +```java +public class BlockClient { + + public static void main(String[] args) throws IOException { + + // 1. 获取通道 + SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666)); + + // 2. 发送一张图片给服务端吧 + FileChannel fileChannel = FileChannel.open(Paths.get("X:\\Users\\ozc\\Desktop\\新建文件夹\\1.png"), StandardOpenOption.READ); + + // 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢 + ByteBuffer buffer = ByteBuffer.allocate(1024); + + // 4.读取本地文件(图片),发送到服务器 + while (fileChannel.read(buffer) != -1) { + + // 在读之前都要切换成读模式 + buffer.flip(); + + socketChannel.write(buffer); + + // 读完切换成写模式,能让管道继续读取文件的数据 + buffer.clear(); + } + + // 5. 关闭流 + fileChannel.close(); + socketChannel.close(); + } +} +``` + +服务端: + +```java +public class BlockServer { + + public static void main(String[] args) throws IOException { + + // 1.获取通道 + ServerSocketChannel server = ServerSocketChannel.open(); + + // 2.得到文件通道,将客户端传递过来的图片写到本地项目下(写模式、没有则创建) + FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE); + + // 3. 绑定链接 + server.bind(new InetSocketAddress(6666)); + + // 4. 获取客户端的连接(阻塞的) + SocketChannel client = server.accept(); + + // 5. 要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢 + ByteBuffer buffer = ByteBuffer.allocate(1024); + + // 6.将客户端传递过来的图片保存在本地中 + while (client.read(buffer) != -1) { + + // 在读之前都要切换成读模式 + buffer.flip(); + + outChannel.write(buffer); + + // 读完切换成写模式,能让管道继续读取文件的数据 + buffer.clear(); + + } + + // 7.关闭通道 + outChannel.close(); + client.close(); + server.close(); + } +} +``` + +结果就可以将客户端传递过来的图片保存在本地了: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/network-connect-7545f4e4-dda9-4e62-8463-58f821cb51ed.jpg) + + + +此时服务端保存完图片想要告诉客户端已经收到图片啦: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/network-connect-c25d8b70-cd8f-4f4b-90eb-2e471deeb958.jpg) + + + +客户端接收服务端带过来的数据: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/network-connect-a7841446-4eed-4b7b-a815-0d8666b4dd44.jpg) + + + +如果仅仅是上面的代码**是不行**的!这个程序会**阻塞**起来! + +* 因为服务端**不知道客户端还有没有数据要发过来**(与刚开始不一样,客户端发完数据就将流关闭了,服务端可以知道客户端没数据发过来了),导致服务端一直在读取客户端发过来的数据。 +* 进而导致了阻塞! + +于是客户端在写完数据给服务端时,**显式告诉服务端已经发完数据**了! + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/network-connect-97c88ca9-1b0c-4cd0-b410-60d059605ee2.jpg) + + + +## NIO非阻塞形态 + +如果使用非阻塞模式的话,那么我们就可以不显式告诉服务器已经发完数据了。我们下面来看看怎么写: + +**客户端**: + +```java +public class NoBlockClient { + + public static void main(String[] args) throws IOException { + + // 1. 获取通道 + SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666)); + + // 1.1切换成非阻塞模式 + socketChannel.configureBlocking(false); + + // 2. 发送一张图片给服务端吧 + FileChannel fileChannel = FileChannel.open(Paths.get("X:\\Users\\ozc\\Desktop\\新建文件夹\\1.png"), StandardOpenOption.READ); + + // 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢 + ByteBuffer buffer = ByteBuffer.allocate(1024); + + // 4.读取本地文件(图片),发送到服务器 + while (fileChannel.read(buffer) != -1) { + + // 在读之前都要切换成读模式 + buffer.flip(); + + socketChannel.write(buffer); + + // 读完切换成写模式,能让管道继续读取文件的数据 + buffer.clear(); + } + + // 5. 关闭流 + fileChannel.close(); + socketChannel.close(); + } +} +``` + +**服务端**: + +```java +public class NoBlockServer { + + public static void main(String[] args) throws IOException { + + // 1.获取通道 + ServerSocketChannel server = ServerSocketChannel.open(); + + // 2.切换成非阻塞模式 + server.configureBlocking(false); + + // 3. 绑定连接 + server.bind(new InetSocketAddress(6666)); + + // 4. 获取选择器 + Selector selector = Selector.open(); + + // 4.1将通道注册到选择器上,指定接收“监听通道”事件 + server.register(selector, SelectionKey.OP_ACCEPT); + + // 5. 轮训地获取选择器上已“就绪”的事件--->只要select()>0,说明已就绪 + while (selector.select() > 0) { + // 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件) + Iterator iterator = selector.selectedKeys().iterator(); + + // 7. 获取已“就绪”的事件,(不同的事件做不同的事) + while (iterator.hasNext()) { + + SelectionKey selectionKey = iterator.next(); + + // 接收事件就绪 + if (selectionKey.isAcceptable()) { + + // 8. 获取客户端的链接 + SocketChannel client = server.accept(); + + // 8.1 切换成非阻塞状态 + client.configureBlocking(false); + + // 8.2 注册到选择器上-->拿到客户端的连接为了读取通道的数据(监听读就绪事件) + client.register(selector, SelectionKey.OP_READ); + + } else if (selectionKey.isReadable()) { // 读事件就绪 + // 9. 获取当前选择器读就绪状态的通道 + SocketChannel client = (SocketChannel) selectionKey.channel(); + + // 9.1读取数据 + ByteBuffer buffer = ByteBuffer.allocate(1024); + + // 9.2得到文件通道,将客户端传递过来的图片写到本地项目下(写模式、没有则创建) + FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE); + + while (client.read(buffer) > 0) { + // 在读之前都要切换成读模式 + buffer.flip(); + + outChannel.write(buffer); + + // 读完切换成写模式,能让管道继续读取文件的数据 + buffer.clear(); + } + } + // 10. 取消选择键(已经处理过的事件,就应该取消掉了) + iterator.remove(); + } + } + + } +} +``` + +还是刚才的需求:**服务端保存了图片以后,告诉客户端已经收到图片了**。 + +在服务端上只要在后面写些数据给客户端就好了: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/network-connect-5ea2f6d3-be10-4703-aa99-bf36e30fab77.jpg) + + + +在客户端上要想获取得到服务端的数据,也需要注册在register上(监听读事件)! + +```java +public class NoBlockClient2 { + + public static void main(String[] args) throws IOException { + + // 1. 获取通道 + SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666)); + + // 1.1切换成非阻塞模式 + socketChannel.configureBlocking(false); + + // 1.2获取选择器 + Selector selector = Selector.open(); + + // 1.3将通道注册到选择器中,获取服务端返回的数据 + socketChannel.register(selector, SelectionKey.OP_READ); + + // 2. 发送一张图片给服务端吧 + FileChannel fileChannel = FileChannel.open(Paths.get("X:\\Users\\ozc\\Desktop\\新建文件夹\\1.png"), StandardOpenOption.READ); + + // 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢 + ByteBuffer buffer = ByteBuffer.allocate(1024); + + // 4.读取本地文件(图片),发送到服务器 + while (fileChannel.read(buffer) != -1) { + + // 在读之前都要切换成读模式 + buffer.flip(); + + socketChannel.write(buffer); + + // 读完切换成写模式,能让管道继续读取文件的数据 + buffer.clear(); + } + + // 5. 轮训地获取选择器上已“就绪”的事件--->只要select()>0,说明已就绪 + while (selector.select() > 0) { + // 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件) + Iterator iterator = selector.selectedKeys().iterator(); + + // 7. 获取已“就绪”的事件,(不同的事件做不同的事) + while (iterator.hasNext()) { + + SelectionKey selectionKey = iterator.next(); + + // 8. 读事件就绪 + if (selectionKey.isReadable()) { + + // 8.1得到对应的通道 + SocketChannel channel = (SocketChannel) selectionKey.channel(); + + ByteBuffer responseBuffer = ByteBuffer.allocate(1024); + + // 9. 知道服务端要返回响应的数据给客户端,客户端在这里接收 + int readBytes = channel.read(responseBuffer); + + if (readBytes > 0) { + // 切换读模式 + responseBuffer.flip(); + System.out.println(new String(responseBuffer.array(), 0, readBytes)); + } + } + + // 10. 取消选择键(已经处理过的事件,就应该取消掉了) + iterator.remove(); + } + } + } + +} +``` + +测试结果: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/network-connect-b69665b3-77f9-4f3d-9075-ffac1489637f.jpg) + + + +下面就**简单总结一下**使用NIO时的要点: + +* 将Socket通道注册到Selector中,监听感兴趣的事件 +* 当感兴趣的时间就绪时,则会进去我们处理的方法进行处理 +* 每处理完一次就绪事件,删除该选择键(因为我们已经处理完了) + +## 4.4管道和DataGramChannel + +这里我就不再讲述了,最难的TCP都讲了,UDP就很简单了。 + +UDP: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/network-connect-7fc34dd6-bab4-4c4a-af8a-6ab898c4b6e9.jpg) + + + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/network-connect-d1e531f8-9638-4fd5-9f3a-70db2c25d92e.jpg) + + + +管道: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/network-connect-86f3b103-0c1c-47fb-8364-e7ec3d91b8b1.jpg) + + + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/network-connect-29d105c0-6525-4efc-912c-d85abd878e82.jpg) + + +>参考链接:[https://www.zhihu.com/question/29005375/answer/667616386](https://www.zhihu.com/question/29005375/answer/667616386),整理:沉默王二 + +--------- + +最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html) + +关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。 + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png) \ No newline at end of file diff --git a/docs/nio/nio.md b/docs/nio/nio.md deleted file mode 100644 index c1ffcfce50ad1616374a58da4db6fe4bf5be6358..0000000000000000000000000000000000000000 --- a/docs/nio/nio.md +++ /dev/null @@ -1,333 +0,0 @@ ---- -title: 一文彻底理解 Java NIO 核心组件 -shortTitle: 一文彻底理解NIO核心组件 -category: - - Java核心 -tag: - - Java NIO -description: Java程序员进阶之路,小白的零基础Java教程,一文彻底理解 Java NIO 核心组件 -head: - - - meta - - name: keywords - content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,Java IO,java NIO,nio ---- - -**同步、异步、阻塞、非阻塞** - -首先,这几个概念非常容易搞混淆,但NIO中又有涉及,所以总结一下。 - -* 同步:API调用返回时调用者就知道操作的结果如何了(实际读取/写入了多少字节)。 -* 异步:相对于同步,API调用返回时调用者不知道操作的结果,后面才会回调通知结果。 -* 阻塞:当无数据可读,或者不能写入所有数据时,挂起当前线程等待。 -* 非阻塞:读取时,可以读多少数据就读多少然后返回,写入时,可以写入多少数据就写入多少然后返回。 - -对于I/O操作,根据Oracle官网的文档,同步异步的划分标准是“调用者是否需要等待I/O操作完成”,这个“等待I/O操作完成”的意思不是指一定要读取到数据或者说写入所有数据,而是指真正进行I/O操作时,比如数据在TCP/IP协议栈缓冲区和JVM缓冲区之间传输的这段时间,调用者是否要等待。 - -所以,我们常用的 read() 和 write() 方法都是同步I/O,同步I/O又分为阻塞和非阻塞两种模式,如果是非阻塞模式,检测到无数据可读时,直接就返回了,并没有真正执行I/O操作。 - -总结就是,Java中实际上只有 同步阻塞I/O、同步非阻塞I/O 与 异步I/O 三种机制,我们下文所说的是前两种,JDK 1.7才开始引入异步 I/O,那称之为NIO.2。 - -## 传统IO - -我们知道,一个新技术的出现总是伴随着改进和提升,Java NIO的出现亦如此。 - -传统 I/O 是阻塞式I/O,主要问题是系统资源的浪费。比如我们为了读取一个TCP连接的数据,调用 InputStream 的 read() 方法,这会使当前线程被挂起,直到有数据到达才被唤醒,那该线程在数据到达这段时间内,占用着内存资源(存储线程栈)却无所作为,也就是俗话说的占着茅坑不拉屎,为了读取其他连接的数据,我们不得不启动另外的线程。在并发连接数量不多的时候,这可能没什么问题,然而当连接数量达到一定规模,内存资源会被大量线程消耗殆尽。另一方面,线程切换需要更改处理器的状态,比如程序计数器、寄存器的值,因此非常频繁的在大量线程之间切换,同样是一种资源浪费。 - -随着技术的发展,现代操作系统提供了新的I/O机制,可以避免这种资源浪费。基于此,诞生了Java NIO,NIO的代表性特征就是非阻塞I/O。紧接着我们发现,简单的使用非阻塞I/O并不能解决问题,因为在非阻塞模式下,read()方法在没有读取到数据时就会立即返回,不知道数据何时到达的我们,只能不停的调用read()方法进行重试,这显然太浪费CPU资源了,从下文可以知道,Selector组件正是为解决此问题而生。 - -## Java NIO 核心组件 - -### 1.Channel - -#### 概念 - -Java NIO中的所有I/O操作都基于Channel对象,就像流操作都要基于Stream对象一样,因此很有必要先了解Channel是什么。以下内容摘自JDK 1.8的文档 - -> A channel represents an open connection to an entity such as a -> -> hardware device, a file, a network socket, or a program component that -> -> is capable of performing one or more distinct I/O operations, for -> -> example reading or writing. - -从上述内容可知,一个Channel(通道)代表和某一实体的连接,这个实体可以是文件、网络套接字等。也就是说,通道是Java NIO提供的一座桥梁,用于我们的程序和操作系统底层I/O服务进行交互。 - -通道是一种很基本很抽象的描述,和不同的I/O服务交互,执行不同的I/O操作,实现不一样,因此具体的有FileChannel、SocketChannel等。 - -通道使用起来跟Stream比较像,可以读取数据到Buffer中,也可以把Buffer中的数据写入通道。 - -![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/segmentfault-yiwrncdlxjavaniohxzjsegmentfaultsp-393f5b9a-8268-4177-ad19-f207b5064466.png) - -当然,也有区别,主要体现在如下两点: - -* 一个通道,既可以读又可以写,而一个Stream是单向的(所以分 InputStream 和 OutputStream) -* 通道有非阻塞I/O模式 - -#### 实现 - -Java NIO中最常用的通道实现是如下几个,可以看出跟传统的 I/O 操作类是一一对应的。 - -* FileChannel:读写文件 -* DatagramChannel: UDP协议网络通信 -* SocketChannel:TCP协议网络通信 -* ServerSocketChannel:监听TCP连接 - -### 2.Buffer - -NIO中所使用的缓冲区不是一个简单的byte数组,而是封装过的Buffer类,通过它提供的API,我们可以灵活的操纵数据,下面细细道来。 - -与Java基本类型相对应,NIO提供了多种 Buffer 类型,如ByteBuffer、CharBuffer、IntBuffer等,区别就是读写缓冲区时的单位长度不一样(以对应类型的变量为单位进行读写)。 - -Buffer中有3个很重要的变量,它们是理解Buffer工作机制的关键,分别是 - -* capacity (总容量) -* position (指针当前位置) -* limit (读/写边界位置) - -Buffer的工作方式跟C语言里的字符数组非常的像,类比一下,capacity就是数组的总长度,position就是我们读/写字符的下标变量,limit就是结束符的位置。Buffer初始时3个变量的情况如下图 - -![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/segmentfault-yiwrncdlxjavaniohxzjsegmentfaultsp-5f1a7222-15cf-41e0-a6c6-729aec5e0a97.png) - -在对Buffer进行读/写的过程中,position会往后移动,而 limit 就是 position 移动的边界。由此不难想象,在对Buffer进行写入操作时,limit应当设置为capacity的大小,而对Buffer进行读取操作时,limit应当设置为数据的实际结束位置。(注意:将Buffer数据 写入 通道是Buffer 读取 操作,从通道 读取 数据到Buffer是Buffer 写入 操作) - -在对Buffer进行读/写操作前,我们可以调用Buffer类提供的一些辅助方法来正确设置 position 和 limit 的值,主要有如下几个 - -* flip(): 设置 limit 为 position 的值,然后 position 置为0。对Buffer进行读取操作前调用。 -* rewind(): 仅仅将 position - -置0。一般是在重新读取Buffer数据前调用,比如要读取同一个Buffer的数据写入多个通道时会用到。 -* clear(): 回到初始状态,即 limit 等于 capacity,position 置0。重新对Buffer进行写入操作前调用。 -* compact(): 将未读取完的数据(position 与 limit 之间的数据)移动到缓冲区开头,并将 position - -设置为这段数据末尾的下一个位置。其实就等价于重新向缓冲区中写入了这么一段数据。 - -然后,看一个实例,使用 FileChannel 读写文本文件,通过这个例子验证通道可读可写的特性以及Buffer的基本用法(注意 FileChannel 不能设置为非阻塞模式)。 - -```java -FileChannel channel = new RandomAccessFile("test.txt", "rw").getChannel(); -channel.position(channel.size()); // 移动文件指针到末尾(追加写入) - -ByteBuffer byteBuffer = ByteBuffer.allocate(20); - -// 数据写入Buffer -byteBuffer.put("你好,世界!\n".getBytes(StandardCharsets.UTF_8)); - -// Buffer -> Channel -byteBuffer.flip(); -while (byteBuffer.hasRemaining()) { - channel.write(byteBuffer); -} - -channel.position(0); // 移动文件指针到开头(从头读取) -CharBuffer charBuffer = CharBuffer.allocate(10); -CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); - -// 读出所有数据 -byteBuffer.clear(); -while (channel.read(byteBuffer) != -1 || byteBuffer.position() > 0) { - byteBuffer.flip(); - - // 使用UTF-8解码器解码 - charBuffer.clear(); - decoder.decode(byteBuffer, charBuffer, false); - System.out.print(charBuffer.flip().toString()); - - byteBuffer.compact(); // 数据可能有剩余 -} - -channel.close(); -``` - -这个例子中使用了两个Buffer,其中 byteBuffer 作为通道读写的数据缓冲区,charBuffer 用于存储解码后的字符。clear() 和 flip() 的用法正如上文所述,需要注意的是最后那个 compact() 方法,即使 charBuffer 的大小完全足以容纳 byteBuffer 解码后的数据,这个 compact() 也必不可少,这是因为常用中文字符的UTF-8编码占3个字节,因此有很大概率出现在中间截断的情况,请看下图: - -![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/segmentfault-yiwrncdlxjavaniohxzjsegmentfaultsp-3e69926a-d4aa-4e1a-ac3d-b699b5a9abe9.png) - -当 Decoder 读取到缓冲区末尾的 0xe4 时,无法将其映射到一个 Unicode,decode()方法第三个参数 false 的作用就是让 Decoder 把无法映射的字节及其后面的数据都视作附加数据,因此 decode() 方法会在此处停止,并且 position 会回退到 0xe4 的位置。如此一来, 缓冲区中就遗留了“中”字编码的第一个字节,必须将其 compact 到前面,以正确的和后序数据拼接起来。 - -BTW,例子中的 CharsetDecoder 也是 Java NIO 的一个新特性,所以大家应该发现了一点哈,NIO的操作是面向缓冲区的(传统I/O是面向流的)。 - -至此,我们了解了 Channel 与 Buffer 的基本用法。接下来要说的是让一个线程管理多个Channel的重要组件。 - -### 3.Selector - -#### Selector 是什么 - -Selector(选择器)是一个特殊的组件,用于采集各个通道的状态(或者说事件)。我们先将通道注册到选择器,并设置好关心的事件,然后就可以通过调用select()方法,静静地等待事件发生。 - -通道有如下4个事件可供我们监听: - -* Accept:有可以接受的连接 -* Connect:连接成功 -* Read:有数据可读 -* Write:可以写入数据了 - -#### 为什么要用Selector - -前文说了,如果用阻塞I/O,需要多线程(浪费内存),如果用非阻塞I/O,需要不断重试(耗费CPU)。Selector的出现解决了这尴尬的问题,非阻塞模式下,通过Selector,我们的线程只为已就绪的通道工作,不用盲目的重试了。比如,当所有通道都没有数据到达时,也就没有Read事件发生,我们的线程会在select()方法处被挂起,从而让出了CPU资源。 - -#### 使用方法 - -如下所示,创建一个Selector,并注册一个Channel。 - -注意:要将 Channel 注册到 Selector,首先需要将 Channel 设置为非阻塞模式,否则会抛异常。 - -```java -Selector selector = Selector.open(); -channel.configureBlocking(false); -SelectionKey key = channel.register(selector, SelectionKey.OP_READ); -``` - -register()方法的第二个参数名叫“interest set”,也就是你所关心的事件集合。如果你关心多个事件,用一个“按位或运算符”分隔,比如 - -```java -SelectionKey.OP_READ | SelectionKey.OP_WRITE复制代码 -``` - -这种写法一点都不陌生,支持位运算的编程语言里都这么玩,用一个整型变量可以标识多种状态,它是怎么做到的呢,其实很简单,举个例子,首先预定义一些常量,它们的值(二进制)如下 - -![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nice-article/segmentfault-yiwrncdlxjavaniohxzjsegmentfaultsp-7a1acc85-7b5b-45d3-996d-79f39b61523d.png) - -可以发现,它们值为1的位都是错开的,因此对它们进行按位或运算之后得出的值就没有二义性,可以反推出是由哪些变量运算而来。怎么判断呢,没错,就是“按位与”运算。比如,现在有一个状态集合变量值为 0011,我们只需要判断 “0011 & OP\_READ” 的值是 1 还是 0 就能确定集合是否包含 OP\_READ 状态。 - -然后,注意 register() 方法返回了一个SelectionKey的对象,这个对象包含了本次注册的信息,我们也可以通过它修改注册信息。从下面完整的例子中可以看到,select()之后,我们也是通过获取一个 SelectionKey 的集合来获取到那些状态就绪了的通道。 - -## 一个完整实例 - -概念和理论的东西阐述完了(其实写到这里,我发现没写出多少东西,好尴尬(⊙ˍ⊙)),看一个完整的例子吧。 - -这个例子使用Java NIO实现了一个单线程的服务端,功能很简单,监听客户端连接,当连接建立后,读取客户端的消息,并向客户端响应一条消息。 - -需要注意的是,我用字符 ‘0′(一个值为0的字节) 来标识消息结束。 - -### 单线程Server - -```java -public class NioServer { - -public static void main(String[] args) throws IOException { - // 创建一个selector - Selector selector = Selector.open(); - - // 初始化TCP连接监听通道 - ServerSocketChannel listenChannel = ServerSocketChannel.open(); - listenChannel.bind(new InetSocketAddress(9999)); - listenChannel.configureBlocking(false); - // 注册到selector(监听其ACCEPT事件) - listenChannel.register(selector, SelectionKey.OP_ACCEPT); - - // 创建一个缓冲区 - ByteBuffer buffer = ByteBuffer.allocate(100); - - while (true) { - selector.select(); //阻塞,直到有监听的事件发生 - Iterator keyIter = selector.selectedKeys().iterator(); - - // 通过迭代器依次访问select出来的Channel事件 - while (keyIter.hasNext()) { - SelectionKey key = keyIter.next(); - - if (key.isAcceptable()) { // 有连接可以接受 - SocketChannel channel = ((ServerSocketChannel) key.channel()).accept(); - channel.configureBlocking(false); - channel.register(selector, SelectionKey.OP_READ); - - System.out.println("与【" + channel.getRemoteAddress() + "】建立了连接!"); - - } else if (key.isReadable()) { // 有数据可以读取 - buffer.clear(); - - // 读取到流末尾说明TCP连接已断开, - // 因此需要关闭通道或者取消监听READ事件 - // 否则会无限循环 - if (((SocketChannel) key.channel()).read(buffer) == -1) { - key.channel().close(); - continue; - } - - // 按字节遍历数据 - buffer.flip(); - while (buffer.hasRemaining()) { - byte b = buffer.get(); - - if (b == 0) { // 客户端消息末尾的\0 - System.out.println(); - - // 响应客户端 - buffer.clear(); - buffer.put("Hello, Client!\0".getBytes()); - buffer.flip(); - while (buffer.hasRemaining()) { - ((SocketChannel) key.channel()).write(buffer); - } - } else { - System.out.print((char) b); - } - } - } - - // 已经处理的事件一定要手动移除 - keyIter.remove(); - } - } -} -} -``` - -### Client - -这个客户端纯粹测试用,为了看起来不那么费劲,就用传统的写法了,代码很简短。 - -要严谨一点测试的话,应该并发运行大量Client,统计服务端的响应时间,而且连接建立后不要立刻发送数据,这样才能发挥出服务端非阻塞I/O的优势。 - -```java -public class Client { - -public static void main(String[] args) throws Exception { - Socket socket = new Socket("localhost", 9999); - InputStream is = socket.getInputStream(); - OutputStream os = socket.getOutputStream(); - - // 先向服务端发送数据 - os.write("Hello, Server!\0".getBytes()); - - // 读取服务端发来的数据 - int b; - while ((b = is.read()) != 0) { - System.out.print((char) b); - } - System.out.println(); - - socket.close(); -} -} -``` - -### NIO vs IO - -学习了NIO之后我们都会有这样一个疑问:到底什么时候该用NIO,什么时候该用传统的I/O呢? - -其实了解他们的特性后,答案还是比较明确的,NIO擅长1个线程管理多条连接,节约系统资源,但是如果每条连接要传输的数据量很大的话,因为是同步I/O,会导致整体的响应速度很慢;而传统I/O为每一条连接创建一个线程,能充分利用处理器并行处理的能力,但是如果连接数量太多,内存资源会很紧张。 - -总结就是:连接数多数据量小用NIO,连接数少用I/O(写起来也简单- -)。 - -## Next - -经过NIO核心组件的学习,了解了非阻塞服务端实现的基本方法。然而,细心的你们肯定也发现了,上面那个完整的例子,实际上就隐藏了很多问题。比如,例子中只是简单的将读取到的每个字节输出,实际环境中肯定是要读取到完整的消息后才能进行下一步处理,由于NIO的非阻塞特性,一次可能只读取到消息的一部分,这已经很糟糕了,如果同一条连接会连续发来多条消息,那不仅要对消息进行拼接,还需要切割,同理,例子中给客户端响应的时候,用了个while()循环,保证数据全部write完成再做其它工作,实际应用中为了性能,肯定不会这么写。另外,为了充分利用现代处理器多核心并行处理的能力,应该用一个线程组来管理这些连接的事件。 - -要解决这些问题,需要一个严谨而繁琐的设计,不过幸运的是,我们有开源的框架可用,那就是优雅而强大的Netty,Netty基于Java NIO,提供异步调用接口,开发高性能服务器的一个很好的选择,之前在项目中使用过,但没有深入学习,打算下一步好好学学它,到时候再写一篇笔记。 - -Java NIO设计的目标是为程序员提供API以享受现代操作系统最新的I/O机制,所以覆盖面较广,除了文中所涉及的组件与特性,还有很多其它的,比如 Pipe(管道)、Path(路径)、Files(文件) 等,有的是用于提升I/O性能的新组件,有的是简化I/O操作的工具,具体用法可以参看最后 References 里的链接。 - - - ->参考链接:[https://segmentfault.com/a/1190000017040893](https://segmentfault.com/a/1190000017040893),整理:沉默王二 - ---------- - -最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html) - -关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。 - - -![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png) \ No newline at end of file diff --git a/docs/nio/rumen.md b/docs/nio/rumen.md new file mode 100644 index 0000000000000000000000000000000000000000..12f74f05a4f43fe8a45136d934002ba8e943ca7e --- /dev/null +++ b/docs/nio/rumen.md @@ -0,0 +1,339 @@ +--- +title: Java NIO 快速入门(buffer缓冲区、Channel管道、Selector选择器) +shortTitle: Java NIO快速入门 +category: + - Java核心 +tag: + - Java NIO +description: Java程序员进阶之路,小白的零基础Java教程,Java NIO 快速入门(buffer缓冲区、Channel管道、Selector选择器) +head: + - - meta + - name: keywords + content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,nio,buffer,channel,selector +--- + +首先我们来看看**IO和NIO的区别**: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-90c84f53-f82d-43dd-87c5-4477e540fa57.jpg) + + + +* 可简单认为:**IO是面向流的处理,NIO是面向块(缓冲区)的处理** + +* 面向流的I/O 系统**一次一个字节地处理数据**。 +* 一个面向块(缓冲区)的I/O系统**以块的形式处理数据**。 + + + +NIO主要有**三个核心部分组成**: + +* **buffer缓冲区** +* **Channel管道** +* **Selector选择器** + +## buffer缓冲区和Channel管道 + +在NIO中并不是以流的方式来处理数据的,而是以buffer缓冲区和Channel管道**配合使用**来处理数据。 + +简单理解一下: + +* Channel管道比作成铁路,buffer缓冲区比作成火车(运载着货物) + +而我们的NIO就是**通过Channel管道运输着存储数据的Buffer缓冲区的来实现数据的处理**! + +* 要时刻记住:Channel不与数据打交道,它只负责运输数据。与数据打交道的是Buffer缓冲区 + +* **Channel-->运输** +* **Buffer-->数据** + + + +相对于传统IO而言,**流是单向的**。对于NIO而言,有了Channel管道这个概念,我们的**读写都是双向**的(铁路上的火车能从广州去北京、自然就能从北京返还到广州)! + +### buffer缓冲区核心要点 + +我们来看看Buffer缓冲区有什么值得我们注意的地方。 + +Buffer是缓冲区的抽象类: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-436aa175-3586-4457-b93c-70b21ff122dc.jpg) + + + +其中ByteBuffer是**用得最多的实现类**(在管道中读写字节数据)。 + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-4bf73cdc-b5e2-4866-ac68-cc57602be5e8.jpg) + + + +拿到一个缓冲区我们往往会做什么?很简单,就是**读取缓冲区的数据/写数据到缓冲区中**。所以,缓冲区的核心方法就是: + +* put() +* get() + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-7229ef4c-a27d-4f90-97d0-8abbfda810a0.jpg) + + + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-df9f0bdf-3afe-42dc-9e7e-459484d7cb8e.jpg) + + + +Buffer类维护了4个核心变量属性来提供**关于其所包含的数组的信息**。它们是: + +* 容量Capacity + +* **缓冲区能够容纳的数据元素的最大数量**。容量在缓冲区创建时被设定,并且永远不能被改变。(不能被改变的原因也很简单,底层是数组嘛) + +* 上界Limit + +* **缓冲区里的数据的总数**,代表了当前缓冲区中一共有多少数据。 + +* 位置Position + +* **下一个要被读或写的元素的位置**。Position会自动由相应的 `get( )`和 `put( )`函数更新。 + +* 标记Mark + +* 一个备忘位置。**用于记录上一次读写的位置**。 + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-85991ca3-99bd-4e56-a84e-e58af4d8aac9.jpg) + + + +### buffer代码演示 + +首先展示一下**是如何创建缓冲区的,核心变量的值是怎么变化的**。 + +```java +public static void main(String[] args) { + + // 创建一个缓冲区 + ByteBuffer byteBuffer = ByteBuffer.allocate(1024); + + // 看一下初始时4个核心变量的值 + System.out.println("初始时-->limit--->"+byteBuffer.limit()); + System.out.println("初始时-->position--->"+byteBuffer.position()); + System.out.println("初始时-->capacity--->"+byteBuffer.capacity()); + System.out.println("初始时-->mark--->" + byteBuffer.mark()); + + System.out.println("--------------------------------------"); + + // 添加一些数据到缓冲区中 + String s = "沉默王二"; + byteBuffer.put(s.getBytes()); + + // 看一下初始时4个核心变量的值 + System.out.println("put完之后-->limit--->"+byteBuffer.limit()); + System.out.println("put完之后-->position--->"+byteBuffer.position()); + System.out.println("put完之后-->capacity--->"+byteBuffer.capacity()); + System.out.println("put完之后-->mark--->" + byteBuffer.mark()); + } +``` + +运行结果: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-cbf8b617-f71c-47f7-bc20-72d46306349f.jpg) + + + +现在**我想要从缓存区拿数据**,怎么拿呀??NIO给了我们一个`flip()`方法。这个方法可以**改动position和limit的位置**! + +还是上面的代码,我们`flip()`一下后,再看看4个核心属性的值会发生什么变化: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-571a843f-1334-4fcb-bbae-90dbbe31ac8c.jpg) + + + +很明显的是: + +* **limit变成了position的位置了** +* **而position变成了0** + +看到这里的同学可能就会想到了:当调用完`filp()`时:**limit是限制读到哪里,而position是从哪里读** + +一般我们称`filp()`为**“切换成读模式”** + +* 每当要从缓存区的时候读取数据时,就调用`filp()`**“切换成读模式”**。 + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-b7d1a7d4-f2a7-4635-b5a8-9d10733df5f3.jpg) + + + +切换成读模式之后,我们就可以读取缓冲区的数据了: + +```java +// 创建一个limit()大小的字节数组(因为就只有limit这么多个数据可读) +byte[] bytes = new byte[byteBuffer.limit()]; + +// 将读取的数据装进我们的字节数组中 +byteBuffer.get(bytes); + +// 输出数据 +System.out.println(new String(bytes, 0, bytes.length)); +``` + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-40d60cff-e87b-4180-a350-7dc5a5207156.jpg) + + + +随后输出一下核心变量的值看看: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-c0fc49ea-bc74-43e8-8f16-d26b93e731bf.jpg) + + + +**读完我们还想写数据到缓冲区**,那就使用`clear()`函数,这个函数会“清空”缓冲区: + +* 数据没有真正被清空,只是被**遗忘**掉了 + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-6567241c-8ca6-492d-a4d1-45e6b275e75e.jpg) + + + +### FileChannel通道核心要点 + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-275c9588-216a-416f-934e-f3fbe54fda43.jpg) + + + +Channel通道**只负责传输数据、不直接操作数据的**。操作数据都是通过Buffer缓冲区来进行操作! + +```java +// 1. 通过本地IO的方式来获取通道 +FileInputStream fileInputStream = new FileInputStream("F:\\3yBlog\\JavaEE常用框架\\Elasticsearch就是这么简单.md"); + +// 得到文件的输入通道 +FileChannel inchannel = fileInputStream.getChannel(); + +// 2. jdk1.7后通过静态方法.open()获取通道 +FileChannel.open(Paths.get("F:\\3yBlog\\JavaEE常用框架\\Elasticsearch就是这么简单2.md"), StandardOpenOption.WRITE); +``` + +使用**FileChannel配合缓冲区**实现文件复制的功能: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-741d14cb-4ea6-43cb-aacc-4fa3297cedba.jpg) + + + +使用**内存映射文件**的方式实现**文件复制**的功能(直接操作缓冲区): + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-c8020177-39b4-405b-abc0-c908ab7cf73d.jpg) + + + +通道之间通过`transfer()`实现数据的传输(直接操作缓冲区): + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-2ce868f7-691e-419e-a443-e25131b2785a.jpg) + + + +### 直接与非直接缓冲区 + +* 非直接缓冲区是**需要**经过一个:copy的阶段的(从内核空间copy到用户空间) +* 直接缓冲区**不需要**经过copy阶段,也可以理解成--->**内存映射文件**,(上面的图片也有过例子)。 + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-c51af71c-759c-40de-9d92-92fffa2d075d.jpg) + + + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-c6181e11-8960-46f3-a4b5-8233d013499c.jpg) + + + +使用直接缓冲区有两种方式: + +* 缓冲区创建的时候分配的是直接缓冲区 +* 在FileChannel上调用`map()`方法,将文件直接映射到内存中创建 + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-16943811-2190-4fc9-82f9-df34c06c22d2.jpg) + + + +### scatter和gather、字符集 + +这个知识点我感觉用得挺少的,不过很多教程都有说这个知识点,我也拿过来说说吧: + +* 分散读取(scatter):将一个通道中的数据分散读取到多个缓冲区中 +* 聚集写入(gather):将多个缓冲区中的数据集中写入到一个通道中 + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-d2b8a337-3c1b-4bce-ae8d-ed107a3676a2.jpg) + + + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-e0916f2c-2ce9-4be6-b071-754301a09642.jpg) + + + +分散读取 + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-886e1838-3404-4bfb-84c9-36cffa19aa19.jpg) + + + +聚集写入 + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-aba6d233-f294-4d1f-b389-dd174e76d1b0.jpg) + + + +字符集(只要编码格式和解码格式一致,就没问题了) + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/rumen-dba55dfc-48df-4111-884d-d67227b7723a.jpg) + +>参考链接:[https://www.zhihu.com/question/29005375/answer/667616386](https://www.zhihu.com/question/29005375/answer/667616386),整理:沉默王二 + +--------- + +最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html) + +关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。 + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png) diff --git a/docs/nio/why.md b/docs/nio/why.md new file mode 100644 index 0000000000000000000000000000000000000000..6f73fea68248701fe5f5154c840c461f87e8de84 --- /dev/null +++ b/docs/nio/why.md @@ -0,0 +1,151 @@ +--- +title: 为什么我们要使用 Java NIO? +shortTitle: 为什么我们要使用Java NIO? +category: + - Java核心 +tag: + - Java NIO +description: Java程序员进阶之路,小白的零基础Java教程,为什么我们要使用 Java NIO? +head: + - - meta + - name: keywords + content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,nio +--- + +我花了几天去了解**NIO的核心知识点**,期间看了《Java 编程思想》和《疯狂Java 讲义》的nio模块。**但是**,会发现看完了之后还是很**迷**,不知道NIO这是干嘛用的,而网上的资料与书上的知识点没有很好地对应。 + +* 网上的资料很多都以IO的五种模型为基础来讲解NIO,而IO这五种模型其中又涉及到了很多概念:`同步/异步/阻塞/非阻塞/多路复用`,**而不同的人又有不同的理解方式**。 +* 还有涉及到了unix的`select/epoll/poll/pselect`,`fd`这些关键字,没有相关基础的人看起来简直是天书 +* 这就导致了在初学时认为nio远不可及 + +我在找资料的过程中也收藏了好多讲解NIO的资料,这篇文章就是**以初学的角度来理解NIO**。也算是我这两天看NIO的一个总结吧。 + +* 希望大家可以看了之后知道什么是NIO,NIO的核心知识点是什么,会使用NIO~ + +那么接下来就开始吧,如果文章有错误的地方请大家多多包涵,不吝在评论区指正哦~ + +> 声明:本文使用JDK1.8 + +JDK 1.4中的`java.nio.*包`中引入新的Java I/O库,其目的是**提高速度**。实际上,“旧”的I/O包已经使用NIO**重新实现过,即使我们不显式的使用NIO编程,也能从中受益**。 + +* nio翻译成 no-blocking io 或者 new io 都无所谓啦,都说得通~ + +在《Java编程思想》读到**“即使我们不显式的使用NIO编程,也能从中受益”**的时候,我是挺在意的,所以:我们**测试**一下使用NIO复制文件和传统IO复制文件的性能: + +```java +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +public class SimpleFileTransferTest { + + private long transferFile(File source, File des) throws IOException { + long startTime = System.currentTimeMillis(); + + if (!des.exists()) + des.createNewFile(); + + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source)); + BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(des)); + + //将数据源读到的内容写入目的地--使用数组 + byte[] bytes = new byte[1024 * 1024]; + int len; + while ((len = bis.read(bytes)) != -1) { + bos.write(bytes, 0, len); + } + + long endTime = System.currentTimeMillis(); + return endTime - startTime; + } + + private long transferFileWithNIO(File source, File des) throws IOException { + long startTime = System.currentTimeMillis(); + + if (!des.exists()) + des.createNewFile(); + + RandomAccessFile read = new RandomAccessFile(source, "rw"); + RandomAccessFile write = new RandomAccessFile(des, "rw"); + + FileChannel readChannel = read.getChannel(); + FileChannel writeChannel = write.getChannel(); + + ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024);//1M缓冲区 + while (readChannel.read(byteBuffer) > 0) { + byteBuffer.flip(); + writeChannel.write(byteBuffer); + byteBuffer.clear(); + } + + writeChannel.close(); + readChannel.close(); + long endTime = System.currentTimeMillis(); + return endTime - startTime; + } + + public static void main(String[] args) throws IOException { + SimpleFileTransferTest simpleFileTransferTest = new SimpleFileTransferTest(); + File sourse = new File("F:\\电影\\[电影天堂www.dygod.cn]猜火车-cd1.rmvb"); + File des = new File("X:\\Users\\ozc\\Desktop\\io.avi"); + File nio = new File("X:\\Users\\ozc\\Desktop\\nio.avi"); + + long time = simpleFileTransferTest.transferFile(sourse, des); + System.out.println(time + ":普通字节流时间"); + + long timeNio = simpleFileTransferTest.transferFileWithNIO(sourse, nio); + System.out.println(timeNio + ":NIO时间"); + + } + +} +``` + +我分别测试了文件大小为13M,40M,200M的: + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/why-d5118350-471f-4998-abb2-4e82c7a50344.jpg) + + + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/why-ffcb8770-5f0a-41e9-8534-f92a6f931a49.jpg) + + + + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/nio/why-0425087f-7878-466b-b02a-a802444e7405.jpg) + + + +为什么要使用NIO? + +可以看到使用过NIO重新实现过的**传统IO根本不虚**,在大文件下效果还比NIO要好(当然了,个人几次的测试,或许不是很准) + +* 而NIO要有一定的学习成本,也没有传统IO那么好理解。 + +那这意味着我们**可以不使用/学习NIO了吗**? + +答案是**否定**的,IO操作往往在**两个场景**下会用到: + +* 文件IO +* 网络IO + +NIO的**魅力:在网络中使用IO就可以体现出来了**! + +* 后面会说到网络中使用NIO,不急哈~ + + +>参考链接:[https://www.zhihu.com/question/29005375/answer/667616386](https://www.zhihu.com/question/29005375/answer/667616386),整理:沉默王二 + +--------- + +最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html) + +关注二哥的原创公众号 **沉默王二**,回复**111** 即可免费领取。 + + +![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/xingbiaogongzhonghao.png) diff --git a/images/nio/javanioxxbjy-1ddb86e5-8a1b-438b-ba0b-1138cfb3be3d.png b/images/nio/javanioxxbjy-1ddb86e5-8a1b-438b-ba0b-1138cfb3be3d.png new file mode 100644 index 0000000000000000000000000000000000000000..7de6e35e355b6c0568f607537778c00363752b84 Binary files /dev/null and b/images/nio/javanioxxbjy-1ddb86e5-8a1b-438b-ba0b-1138cfb3be3d.png differ diff --git a/images/nio/javanioxxbjy-61a9680f-acec-4851-928e-9bfb7d1dd820.png b/images/nio/javanioxxbjy-61a9680f-acec-4851-928e-9bfb7d1dd820.png new file mode 100644 index 0000000000000000000000000000000000000000..84cc6ae7ea65c8b664d9880cdb00474f14f38f86 Binary files /dev/null and b/images/nio/javanioxxbjy-61a9680f-acec-4851-928e-9bfb7d1dd820.png differ diff --git a/images/nio/javanioxxbjy-86c80575-1a15-42d2-9040-cfcfe0bbb470.png b/images/nio/javanioxxbjy-86c80575-1a15-42d2-9040-cfcfe0bbb470.png new file mode 100644 index 0000000000000000000000000000000000000000..9b013054bfba5961df42a580bec5c9ab33cb0b97 Binary files /dev/null and b/images/nio/javanioxxbjy-86c80575-1a15-42d2-9040-cfcfe0bbb470.png differ diff --git a/images/nio/moxing-54ee4738-b689-4026-863f-13e456b374de.jpg b/images/nio/moxing-54ee4738-b689-4026-863f-13e456b374de.jpg new file mode 100644 index 0000000000000000000000000000000000000000..967ae55ddf49db54183d42f41bffcbe78d9ce795 Binary files /dev/null and b/images/nio/moxing-54ee4738-b689-4026-863f-13e456b374de.jpg differ diff --git a/images/nio/moxing-62def8ad-3ca3-467b-81f6-5d0a31dd7fdc.jpg b/images/nio/moxing-62def8ad-3ca3-467b-81f6-5d0a31dd7fdc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..97552b08e4db79eeec843aedf118a4aa61772a9e Binary files /dev/null and b/images/nio/moxing-62def8ad-3ca3-467b-81f6-5d0a31dd7fdc.jpg differ diff --git a/images/nio/moxing-6590a3de-0e7c-4ce2-aa1c-815625095e62.jpg b/images/nio/moxing-6590a3de-0e7c-4ce2-aa1c-815625095e62.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5541efdc725c70a8100d57ca5ca8c9713f1bbf6e Binary files /dev/null and b/images/nio/moxing-6590a3de-0e7c-4ce2-aa1c-815625095e62.jpg differ diff --git a/images/nio/moxing-8a1cb207-6c56-4bd8-8489-c21d5a76e1ca.jpg b/images/nio/moxing-8a1cb207-6c56-4bd8-8489-c21d5a76e1ca.jpg new file mode 100644 index 0000000000000000000000000000000000000000..826604d8ce17a56a4c638faf2c38cffe27166eef Binary files /dev/null and b/images/nio/moxing-8a1cb207-6c56-4bd8-8489-c21d5a76e1ca.jpg differ diff --git a/images/nio/moxing-aec90e84-33c5-4f5b-997e-8db54d6bce88.jpg b/images/nio/moxing-aec90e84-33c5-4f5b-997e-8db54d6bce88.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e827015929513efc450ae0437544eca4f2044515 Binary files /dev/null and b/images/nio/moxing-aec90e84-33c5-4f5b-997e-8db54d6bce88.jpg differ diff --git a/images/nio/network-connect-29d105c0-6525-4efc-912c-d85abd878e82.jpg b/images/nio/network-connect-29d105c0-6525-4efc-912c-d85abd878e82.jpg new file mode 100644 index 0000000000000000000000000000000000000000..de4ecb0f534ee249b4022d0c8355e501cff02424 Binary files /dev/null and b/images/nio/network-connect-29d105c0-6525-4efc-912c-d85abd878e82.jpg differ diff --git a/images/nio/network-connect-5ea2f6d3-be10-4703-aa99-bf36e30fab77.jpg b/images/nio/network-connect-5ea2f6d3-be10-4703-aa99-bf36e30fab77.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4edb28bf040d43f532580941b9507aa8101bed3f Binary files /dev/null and b/images/nio/network-connect-5ea2f6d3-be10-4703-aa99-bf36e30fab77.jpg differ diff --git a/images/nio/network-connect-63f45193-8eb7-4cc5-a5a7-70713fac0d73.jpg b/images/nio/network-connect-63f45193-8eb7-4cc5-a5a7-70713fac0d73.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c4799b429d7b2d793ce91cdafdd964b729824b2d Binary files /dev/null and b/images/nio/network-connect-63f45193-8eb7-4cc5-a5a7-70713fac0d73.jpg differ diff --git a/images/nio/network-connect-7545f4e4-dda9-4e62-8463-58f821cb51ed.jpg b/images/nio/network-connect-7545f4e4-dda9-4e62-8463-58f821cb51ed.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3b423a1c63860b9aad8032843a86b8e7b42ff19b Binary files /dev/null and b/images/nio/network-connect-7545f4e4-dda9-4e62-8463-58f821cb51ed.jpg differ diff --git a/images/nio/network-connect-7fc34dd6-bab4-4c4a-af8a-6ab898c4b6e9.jpg b/images/nio/network-connect-7fc34dd6-bab4-4c4a-af8a-6ab898c4b6e9.jpg new file mode 100644 index 0000000000000000000000000000000000000000..93598bf74d03124e4c69df718216d17a8700f1e5 Binary files /dev/null and b/images/nio/network-connect-7fc34dd6-bab4-4c4a-af8a-6ab898c4b6e9.jpg differ diff --git a/images/nio/network-connect-86f3b103-0c1c-47fb-8364-e7ec3d91b8b1.jpg b/images/nio/network-connect-86f3b103-0c1c-47fb-8364-e7ec3d91b8b1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..377f839006e048d68498273dd390ba49eb0ff812 Binary files /dev/null and b/images/nio/network-connect-86f3b103-0c1c-47fb-8364-e7ec3d91b8b1.jpg differ diff --git a/images/nio/network-connect-97c88ca9-1b0c-4cd0-b410-60d059605ee2.jpg b/images/nio/network-connect-97c88ca9-1b0c-4cd0-b410-60d059605ee2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e45f1c4ca11ae81fa67eb62197ed295adae68fb5 Binary files /dev/null and b/images/nio/network-connect-97c88ca9-1b0c-4cd0-b410-60d059605ee2.jpg differ diff --git a/images/nio/network-connect-a7841446-4eed-4b7b-a815-0d8666b4dd44.jpg b/images/nio/network-connect-a7841446-4eed-4b7b-a815-0d8666b4dd44.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fa42eef42b368eb6464aa051681ee078b80e5b89 Binary files /dev/null and b/images/nio/network-connect-a7841446-4eed-4b7b-a815-0d8666b4dd44.jpg differ diff --git a/images/nio/network-connect-b69665b3-77f9-4f3d-9075-ffac1489637f.jpg b/images/nio/network-connect-b69665b3-77f9-4f3d-9075-ffac1489637f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2e52b3779e9690d8d58c9a491c09d42aaf561b38 Binary files /dev/null and b/images/nio/network-connect-b69665b3-77f9-4f3d-9075-ffac1489637f.jpg differ diff --git a/images/nio/network-connect-ba836b5c-82d2-42b0-b1ae-83f8ee0b0101.jpg b/images/nio/network-connect-ba836b5c-82d2-42b0-b1ae-83f8ee0b0101.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6f97b988d8dc1304de7c6074fdbd2e69cf336867 Binary files /dev/null and b/images/nio/network-connect-ba836b5c-82d2-42b0-b1ae-83f8ee0b0101.jpg differ diff --git a/images/nio/network-connect-bb1bd676-8aeb-4428-9498-230a05ee717d.jpg b/images/nio/network-connect-bb1bd676-8aeb-4428-9498-230a05ee717d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e5c31d8effb806131a1f27668240a7c37d358038 Binary files /dev/null and b/images/nio/network-connect-bb1bd676-8aeb-4428-9498-230a05ee717d.jpg differ diff --git a/images/nio/network-connect-c25d8b70-cd8f-4f4b-90eb-2e471deeb958.jpg b/images/nio/network-connect-c25d8b70-cd8f-4f4b-90eb-2e471deeb958.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0741011fb18c8d3fd147205279002ecda0797cee Binary files /dev/null and b/images/nio/network-connect-c25d8b70-cd8f-4f4b-90eb-2e471deeb958.jpg differ diff --git a/images/nio/network-connect-d1e531f8-9638-4fd5-9f3a-70db2c25d92e.jpg b/images/nio/network-connect-d1e531f8-9638-4fd5-9f3a-70db2c25d92e.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6027f04a09318c44086abcae0cc7e5aab3b576e9 Binary files /dev/null and b/images/nio/network-connect-d1e531f8-9638-4fd5-9f3a-70db2c25d92e.jpg differ diff --git a/images/nio/rumen-16943811-2190-4fc9-82f9-df34c06c22d2.jpg b/images/nio/rumen-16943811-2190-4fc9-82f9-df34c06c22d2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..63c646bab13a054e1c8e4f0ba069ce8bbd973b07 Binary files /dev/null and b/images/nio/rumen-16943811-2190-4fc9-82f9-df34c06c22d2.jpg differ diff --git a/images/nio/rumen-275c9588-216a-416f-934e-f3fbe54fda43.jpg b/images/nio/rumen-275c9588-216a-416f-934e-f3fbe54fda43.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d3f2f728a4761fe591fabb12d293ce380e632598 Binary files /dev/null and b/images/nio/rumen-275c9588-216a-416f-934e-f3fbe54fda43.jpg differ diff --git a/images/nio/rumen-2ce868f7-691e-419e-a443-e25131b2785a.jpg b/images/nio/rumen-2ce868f7-691e-419e-a443-e25131b2785a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a2e8a9198b7ad0bc8a474bdb7f9b93cdf604fda5 Binary files /dev/null and b/images/nio/rumen-2ce868f7-691e-419e-a443-e25131b2785a.jpg differ diff --git a/images/nio/rumen-40d60cff-e87b-4180-a350-7dc5a5207156.jpg b/images/nio/rumen-40d60cff-e87b-4180-a350-7dc5a5207156.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8041cc8413a895f92fb1e965e54ca28b97cbdad1 Binary files /dev/null and b/images/nio/rumen-40d60cff-e87b-4180-a350-7dc5a5207156.jpg differ diff --git a/images/nio/rumen-436aa175-3586-4457-b93c-70b21ff122dc.jpg b/images/nio/rumen-436aa175-3586-4457-b93c-70b21ff122dc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d4fdb8258fbaf541a23672efe07d31e5dcc2b81d Binary files /dev/null and b/images/nio/rumen-436aa175-3586-4457-b93c-70b21ff122dc.jpg differ diff --git a/images/nio/rumen-4bf73cdc-b5e2-4866-ac68-cc57602be5e8.jpg b/images/nio/rumen-4bf73cdc-b5e2-4866-ac68-cc57602be5e8.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ebc4f7ee22fdc0f3a90fa5b3638a0f0a8b5ad6e8 Binary files /dev/null and b/images/nio/rumen-4bf73cdc-b5e2-4866-ac68-cc57602be5e8.jpg differ diff --git a/images/nio/rumen-571a843f-1334-4fcb-bbae-90dbbe31ac8c.jpg b/images/nio/rumen-571a843f-1334-4fcb-bbae-90dbbe31ac8c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cedd1bed86a95ad2f9ba284aef911cbc90b7df7b Binary files /dev/null and b/images/nio/rumen-571a843f-1334-4fcb-bbae-90dbbe31ac8c.jpg differ diff --git a/images/nio/rumen-6567241c-8ca6-492d-a4d1-45e6b275e75e.jpg b/images/nio/rumen-6567241c-8ca6-492d-a4d1-45e6b275e75e.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f2ac6bb398c67e71d9bbfe9e09a9531faf366dcd Binary files /dev/null and b/images/nio/rumen-6567241c-8ca6-492d-a4d1-45e6b275e75e.jpg differ diff --git a/images/nio/rumen-7229ef4c-a27d-4f90-97d0-8abbfda810a0.jpg b/images/nio/rumen-7229ef4c-a27d-4f90-97d0-8abbfda810a0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d2068eac5f0d366a652f0c916997e22d62e75e22 Binary files /dev/null and b/images/nio/rumen-7229ef4c-a27d-4f90-97d0-8abbfda810a0.jpg differ diff --git a/images/nio/rumen-741d14cb-4ea6-43cb-aacc-4fa3297cedba.jpg b/images/nio/rumen-741d14cb-4ea6-43cb-aacc-4fa3297cedba.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0480e8b72c09fa4b93ed614d639a9c1cba381c3c Binary files /dev/null and b/images/nio/rumen-741d14cb-4ea6-43cb-aacc-4fa3297cedba.jpg differ diff --git a/images/nio/rumen-85991ca3-99bd-4e56-a84e-e58af4d8aac9.jpg b/images/nio/rumen-85991ca3-99bd-4e56-a84e-e58af4d8aac9.jpg new file mode 100644 index 0000000000000000000000000000000000000000..23b6bc7f4b4b0d35c7f21e30a490e05eac54d764 Binary files /dev/null and b/images/nio/rumen-85991ca3-99bd-4e56-a84e-e58af4d8aac9.jpg differ diff --git a/images/nio/rumen-886e1838-3404-4bfb-84c9-36cffa19aa19.jpg b/images/nio/rumen-886e1838-3404-4bfb-84c9-36cffa19aa19.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0afa668af8e2c8939814acb18f8504b0a9485c2a Binary files /dev/null and b/images/nio/rumen-886e1838-3404-4bfb-84c9-36cffa19aa19.jpg differ diff --git a/images/nio/rumen-90c84f53-f82d-43dd-87c5-4477e540fa57.jpg b/images/nio/rumen-90c84f53-f82d-43dd-87c5-4477e540fa57.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e5c31d8effb806131a1f27668240a7c37d358038 Binary files /dev/null and b/images/nio/rumen-90c84f53-f82d-43dd-87c5-4477e540fa57.jpg differ diff --git a/images/nio/rumen-aba6d233-f294-4d1f-b389-dd174e76d1b0.jpg b/images/nio/rumen-aba6d233-f294-4d1f-b389-dd174e76d1b0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b516f8eeb0831666c6a2b4736e5e22e364427aa8 Binary files /dev/null and b/images/nio/rumen-aba6d233-f294-4d1f-b389-dd174e76d1b0.jpg differ diff --git a/images/nio/rumen-b7d1a7d4-f2a7-4635-b5a8-9d10733df5f3.jpg b/images/nio/rumen-b7d1a7d4-f2a7-4635-b5a8-9d10733df5f3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b3191a6cdb03f85961220103f6266662ca303be0 Binary files /dev/null and b/images/nio/rumen-b7d1a7d4-f2a7-4635-b5a8-9d10733df5f3.jpg differ diff --git a/images/nio/rumen-c0fc49ea-bc74-43e8-8f16-d26b93e731bf.jpg b/images/nio/rumen-c0fc49ea-bc74-43e8-8f16-d26b93e731bf.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0a1bb5f0b11b4634723114d4839aebd766a2b3fb Binary files /dev/null and b/images/nio/rumen-c0fc49ea-bc74-43e8-8f16-d26b93e731bf.jpg differ diff --git a/images/nio/rumen-c51af71c-759c-40de-9d92-92fffa2d075d.jpg b/images/nio/rumen-c51af71c-759c-40de-9d92-92fffa2d075d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e5bd4732920b8e4d091bad537fcc5735c3384c06 Binary files /dev/null and b/images/nio/rumen-c51af71c-759c-40de-9d92-92fffa2d075d.jpg differ diff --git a/images/nio/rumen-c6181e11-8960-46f3-a4b5-8233d013499c.jpg b/images/nio/rumen-c6181e11-8960-46f3-a4b5-8233d013499c.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a2cc4a3184bb6e786cd69760321e37d7ea94943c Binary files /dev/null and b/images/nio/rumen-c6181e11-8960-46f3-a4b5-8233d013499c.jpg differ diff --git a/images/nio/rumen-c8020177-39b4-405b-abc0-c908ab7cf73d.jpg b/images/nio/rumen-c8020177-39b4-405b-abc0-c908ab7cf73d.jpg new file mode 100644 index 0000000000000000000000000000000000000000..16c8ebe5949345465fb4a091375de6265a641238 Binary files /dev/null and b/images/nio/rumen-c8020177-39b4-405b-abc0-c908ab7cf73d.jpg differ diff --git a/images/nio/rumen-cbf8b617-f71c-47f7-bc20-72d46306349f.jpg b/images/nio/rumen-cbf8b617-f71c-47f7-bc20-72d46306349f.jpg new file mode 100644 index 0000000000000000000000000000000000000000..730f55a40f4c1cddabd347bb7efa33e29f549200 Binary files /dev/null and b/images/nio/rumen-cbf8b617-f71c-47f7-bc20-72d46306349f.jpg differ diff --git a/images/nio/rumen-d2b8a337-3c1b-4bce-ae8d-ed107a3676a2.jpg b/images/nio/rumen-d2b8a337-3c1b-4bce-ae8d-ed107a3676a2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1fc37f71284d318486503e4882a9b860db9fd6a5 Binary files /dev/null and b/images/nio/rumen-d2b8a337-3c1b-4bce-ae8d-ed107a3676a2.jpg differ diff --git a/images/nio/rumen-dba55dfc-48df-4111-884d-d67227b7723a.jpg b/images/nio/rumen-dba55dfc-48df-4111-884d-d67227b7723a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3f0ee340fcae94d99ee8d8163b78f64e45957f63 Binary files /dev/null and b/images/nio/rumen-dba55dfc-48df-4111-884d-d67227b7723a.jpg differ diff --git a/images/nio/rumen-df9f0bdf-3afe-42dc-9e7e-459484d7cb8e.jpg b/images/nio/rumen-df9f0bdf-3afe-42dc-9e7e-459484d7cb8e.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e890233e287112856d42b83060f077dc1dc1cf51 Binary files /dev/null and b/images/nio/rumen-df9f0bdf-3afe-42dc-9e7e-459484d7cb8e.jpg differ diff --git a/images/nio/rumen-e0916f2c-2ce9-4be6-b071-754301a09642.jpg b/images/nio/rumen-e0916f2c-2ce9-4be6-b071-754301a09642.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e229c6639232873243b12e7e4305d9a8b17a56e4 Binary files /dev/null and b/images/nio/rumen-e0916f2c-2ce9-4be6-b071-754301a09642.jpg differ diff --git a/images/nio/why-0425087f-7878-466b-b02a-a802444e7405.jpg b/images/nio/why-0425087f-7878-466b-b02a-a802444e7405.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d9ccf79ff9c0f0d352be45f61a12fea4377d2df6 Binary files /dev/null and b/images/nio/why-0425087f-7878-466b-b02a-a802444e7405.jpg differ diff --git a/images/nio/why-d5118350-471f-4998-abb2-4e82c7a50344.jpg b/images/nio/why-d5118350-471f-4998-abb2-4e82c7a50344.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b04dad84957a87d19076d718d55c7f341a1152a4 Binary files /dev/null and b/images/nio/why-d5118350-471f-4998-abb2-4e82c7a50344.jpg differ diff --git a/images/nio/why-ffcb8770-5f0a-41e9-8534-f92a6f931a49.jpg b/images/nio/why-ffcb8770-5f0a-41e9-8534-f92a6f931a49.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e56d0837c3d83824014527d9154a7fba36c02007 Binary files /dev/null and b/images/nio/why-ffcb8770-5f0a-41e9-8534-f92a6f931a49.jpg differ