一.NIO介绍
-
Java NIO 全称 java non-blocking IO,是指 JDK 从 JDK1.4 开始提供的新 API。Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO),是同步非阻塞的
-
NIO 相关类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写
-
NIO 有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)
NIO是 面向缓冲区 ,或者面向 块 编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。满足我们在高并发环境下的生产需求。
二.NIO和BIO比较
- BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多
- BIO 是阻塞的,NIO 则是非阻塞的
- BIO基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道
三.NIO三大核心的原理示意图
- 每个channel 都会对应一个Buffer
- Selector 对应一个线程, 一个线程对应多个channel(连接)
- 程序切换到哪个channel 是由事件Event决定的, Event 是一个重要的概念。Selector 会根据不同的事件,在各个通道上切换
- 数据的读取写入是通过Buffer, 这个和BIO , BIO 中要么是输入流,或者是 输出流, 不能双向,但是NIO的Buffer 是可以读也可以写, 需要 flip 方法切换
缓冲区(Buffer)
- 缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer
Buffer顶层抽象类的重要变量
所有继承Buffer的子类都有这四个参数
Buffer基本函数用法示例
put:写数据
get:读数据
flip():读写切换
position(int i):定位buffer
public static void main(String[] args) {
//创建一个Buffer, 大小为 5, 即可以存放5个int
IntBuffer intBuffer = IntBuffer.allocate(5);
//向buffer 存放数据 存放了 0 2 4 6 8
for(int i = 0; i < intBuffer.capacity(); i++) {
intBuffer.put( i * 2);
}
//将buffer转换,读写切换(!!!)
intBuffer.flip();//将buffer的读写状态切换 从写--->读
intBuffer.position(1);//position函数能定位buffer的索引位置 设置为1则索引为1 对应的数字是2
System.out.println(intBuffer.get());//输出2
intBuffer.limit(3);
while (intBuffer.hasRemaining()) {
System.out.println(intBuffer.get());
}
}
通道(Channel)
NIO的通道类似于流,但有些区别如下:
- 通道可以同时进行读写,而流只能读或者只能写
- 通道可以实现异步读写数据
- 通道可以从缓冲读数据,也可以写数据到缓冲
Channel在NIO中是一个接口
public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel>
常用的 Channel 类有:FileChannel、DatagramChannel、ServerSocketChannel 和 SocketChannel。【ServerSocketChanne 类似 ServerSocket , SocketChannel 类似 Socket】
channel读写文件实例
输出文件:
文件 <— 输出流 <— FileChannel <— ByteBuffer <— 程序的字符串
public static void main(String[] args) throws Exception{
String str = "hello,world";
//创建一个输出流->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();
}
首先创建输出流,然后通过输出流获得Channel对象,创建缓冲区,将要输出的数据放到缓冲区,再调用flip,最后将缓冲区数据写入Channel对象
读入文件:
文件 —> 输入流 —> FileChannel —> ByteBuffer —> 程序显示
//创建文件的输入流
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();
选择器(Selector)
- Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到Selector(选择器)
- Selector 能够检测多个注册的通道上是否有事件发生(注意:多个Channel以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求
- 只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,避免了多线程之间的上下文切换导致的开销