待补充
1)Java NIO 全称 java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO),是同步非阻塞的。
2)NIO 相关类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写。【基本案例】
3)NIO 有三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)。
4)NIO 是面向缓冲区,或者面向 块 编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。
5)Java NIO 的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他事情。非阻塞写也是如此,一个线程写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。【后面有案例说明】
6)通俗理解:NIO 是可以做到用一个线程来处理多个操作。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个线程处理连接。
7)HTTP2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比 HTTP1.0 大了好几个数量级。
8)案例说明 NIO 的 Buffer:
package com.min.NIO;
import java.nio.IntBuffer;
/**
* 举例说明Buffer 的使用 (简单说明)
*/
public class BasicBuffer {
public static void main(String[] args) {
//1.创建一个Buffer, 大小为 5, 即可以存放5个int类型数据
IntBuffer intBuffer = IntBuffer.allocate(5);
//2.向buffer 存放数据
// 2.1 直接put方式存放数据
// intBuffer.put(10);
// intBuffer.put(11);
// intBuffer.put(12);
// intBuffer.put(13);
// intBuffer.put(14);
// 2.2 通过for循环put存放数据
for(int i = 0; i < intBuffer.capacity(); i++) {
intBuffer.put( i * 2);
}
//3.从buffer 读取数据
/** flip() 将buffer转换,读写切换(!!!)
*
* flip()源码:
* public final Buffer flip() {
* limit = position; //读数据不能超过5
* position = 0;
* mark = -1;
* return this;
* }
*/
intBuffer.flip();
// intBuffer.position(1);//1,2
// System.out.println(intBuffer.get());
// intBuffer.limit(3);
while (intBuffer.hasRemaining()) {
System.out.println(intBuffer.get());
}
}
}
1)BIO 以流的方式处理数据;而 NIO 以块的方式处理数据。块 I/O 的效率比 流 I/O 高很多。
2)BIO 是阻塞的;而 NIO 是非阻塞的。
3)BIO 基于字节流和字符流进行操作;而 NIO 基于 Channel(通道) 和 Buffer(缓冲区) 进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器) 用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。

Channel:通道;Buffer:缓冲区;Selector:选择器;Event:事件
1)每个 Channel 都会对应一个 Buffer;
2)Selector 对应一个线程,一个线程对应多个 Channel(Channel可以理解为 连接);
3)该图反映了有三个 Channel 注册到该 Selector;
4)程序切换到哪个 Channel 是由事件决定的,Event 就是一个重要的概念;
5)Selector 会根据不同的事件,在各个通道上切换;
6)Buffer 就是一个内存块,底层是有一个数组;(所以说 Netty 是面向块编程的,块其实就是指Buffer)
7)数据的读取和写入是通过 Buffer。这点和BIO有本质区别,BIO中要么是输入流,要么是输出流,不能双向。但是 NIO 的 Buffer 是可以读可以写,需要 filp()方法切换;Channel是双向的,可以返回底层操作系统的情况;比如Linux,底层的操作系统通道就是双向的。
缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成就是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够追踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer,如图:

1)在NIO中,Buffer是一个顶层父类,它是一个抽象类,常用Buffer子类有:
| 常用Buffer子类 | 作用 |
|---|---|
| ByteBuffer | 存储字节数据到缓冲区 |
| ShortBuffer | 存储字符串数据到缓冲区 |
| CharBuffer | 存储字符数据到缓存区 |
| IntBuffer | 存储整数数据到缓存区 |
| LongBuffer | 存储长整型数据到缓存区 |
| DoubleBuffer | 存储小数到缓存区 |
| FloatBuffer | 存储小数到缓存区 |
2)Buffer类定义了所有的缓冲区都具有的四个属性,用来提供关于其所包含的数据元素的信息:
| 属性 | 描述 |
|---|---|
| capacity | 容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变 |
| limit | 表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的 |
| position | 位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变该值,为下次读写做准备 |
| mark | 标记,定义为 -1 |
3)Buffer类相关方法有:

从前面可以看出对于 Java 中的基本数据类型(boolean除外),都有一个 Buffer 类型与之相对应,最常用的自然是 ByteBuffer类(二进制数据),该类的主要方法如下:

1)NIO 的通道类似于流,但有些区别,如下:
2)BIO 中的 流(stream )是单向的,例如 FileInputStream 对象只能进行读取数据的操作;而 NIO 中的通道(Channel)是双向的,可以读操作,也可以写操作。
3)Channel 在NIO中是一个接口
public interface Channel extends Closeable{}
4)常用的 Channel类有:
FileChannel 主要用来对本地文件进行 IO 操作,常见的方法有:
1)实例要求:使用前面学习后的ByteBuffer(缓冲)和FileChannel(通道),将"hello,尚硅谷"写入到file01.txt文件中。
2)业务流程如下图左半部分:

3)代码展示:(运行程序之后D盘就会创建一个file01.txt文件,并有写入的字符串数据展示)
package com.min.NIO;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* FileChannel 应用案例1-本地文件写数据
*
* 1.创建文件 file01.txt
* 2.将“hello,尚硅谷”写入到 file01.txt中
*/
public class NIOFileChannel01 {
public static void main(String[] args) throws IOException {
String str = "hello,尚硅谷";
//创建一个输出流->channel
FileOutputStream fileOutputStream = new FileOutputStream("d:\\file01.txt");
//通过 fileOutputStream 获取对应的 FileChannel
//这个 fileChannel 真实类型是 FileChannelImpl
FileChannel fileChannel = fileOutputStream.getChannel();
//创建一个缓冲区 ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//将 str 放入 byteBuffer
byteBuffer.put(str.getBytes());
//对byteBuffer 进行flip(反转,由写模式转为读模式)
byteBuffer.flip();
//将byteBuffer 数据写入到 fileChannel
fileChannel.write(byteBuffer);
fileOutputStream.close();
}
}
1)实例要求:使用前面学习后的ByteBuffer(缓冲)和FileChannel(通道),将file01.txt文件中的数据读入到程序,并显示在控制台屏幕。(因为刚进行案例1创建出来了file01.txt文件,本案例将读取其中的数据信息,若无文件需自己创建)
2)业务流程如下图右部分:

3)代码展示
package com.min.NIO;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* FileChannel 应用案例2-本地文件读数据
*/
public class NIOFileChannel02 {
public static void main(String[] args) throws Exception {
//创建文件的输入流
File file = new File("d:\\file01.txt");
FileInputStream fileInputStream = new FileInputStream(file);
//通过fileInputStream 获取对应的FileChannel -> 实际类型 FileChannelImpl
FileChannel fileChannel = fileInputStream.getChannel();
//创建缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
//将 通道的数据读入到Buffer
fileChannel.read(byteBuffer);
//将byteBuffer 的 字节数据 转成String
System.out.println(new String(byteBuffer.array()));
fileInputStream.close();
}
}
1)实例要求:使用FileChannel(通道)和read/write方法,用一个Buffer完成文件的拷贝,拷贝一个文本文件1.txt到2.txt。(此时需要在本项目录下手动创建文件1.txt,文件2.txt会自动创建)
2)业务流程图:

3)代码展示:
package com.min.NIO;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* 应用案例3-使用一个Buffer完成文件读取、写入
*/
public class NIOFileChannel03 {
public static void main(String[] args) throws Exception {
FileInputStream fileInputStream = new FileInputStream("1.txt");
FileChannel fileChannel01 = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
FileChannel fileChannel02 = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
while (true) { //循环读取
//这里有一个重要的操作,一定不要忘了
/*
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
*/
byteBuffer.clear(); //清空buffer,重置标志位
int read = fileChannel01.read(byteBuffer);
System.out.println("read =" + read);
if(read == -1) { //表示读完
break;
}
//将buffer 中的数据写入到 fileChannel02 -- 2.txt
byteBuffer.flip();
fileChannel02.write(byteBuffer);
}
//关闭相关的流
fileInputStream.close();
fileOutputStream.close();
}
}
1)实例要求:使用FileChannel(通道)和transferFrom方法,完成文件的拷贝,拷贝一张图片。(在D盘中放置一张图片a.jpg,完成拷贝后a2.jpg图片会自动创建出来)
2)代码演示:
package com.atguigu.nio;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
public class NIOFileChannel04 {
public static void main(String[] args) throws Exception {
//创建相关流
FileInputStream fileInputStream = new FileInputStream("d:\\a.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("d:\\a2.jpg");
//获取各个流对应的filechannel
FileChannel sourceCh = fileInputStream.getChannel();
FileChannel destCh = fileOutputStream.getChannel();
//使用transferForm完成拷贝
destCh.transferFrom(sourceCh,0,sourceCh.size());
//关闭相关通道和流
sourceCh.close();
destCh.close();
fileInputStream.close();
fileOutputStream.close();
}
}
1)ByteBuffer 支持类型化的 put 和 get,put放入的是什么数据类型,get就应该使用相应的数据类型来取出,否则可能有 BufferUnderflowException 异常。【举例说明】
2)可以将一个普通Buffer 转成只读Buffer。【举例说明】
3)NIO 还提供了 MappedByteBuffer,可以让文件直接在内存(堆外的内存)中进行修改,而如何同步到文件由 NIO 来完成。【举例说明】
4)前面我们讲的读写操作,都是通过一个Buffer完成的,NIO还支持通过多个Buffer(即Buffer数组)完成读写操作,即 Scattering 和 Gatering。【举例说明】
1)Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)。
2)Selector 能够检测多个注册的通道上是否有事件发生(注意:多个Channel 以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。【示意图如6.2所示】
3)只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。
4)避免了多线程之间的上下文切换导致的开销。

说明如下:
1)Netty 的 IO线程 NioEventLoop 聚合了 Selector(选择器,也叫多路复用器),可以同时并发处理成百上千个客户端连接。
2)当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。
3)线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。
4)由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起。
5)一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。