NIO —— 三大组件
文章目录
- NIO 三大核心原理示意图
- 缓冲区(Buffer)
- 基本 介绍
- Buffer 类及其子类
- Buffer 的读写切换
- 通道(Channel)
- 基本介绍
- 选择器(Selector)
- 基本介绍
- 特点
- 注意事项
- write(ByteBuffer src)
- 举例(写入数据):
- read(ByteBuffer dst)
- 举例(读取数据)
- 举例(拷贝文件)
- ByteBuffer
- transferFrom
- 关于 Buffer 和 Channel 的注意事项和细节
NIO 三大核心原理示意图
Selector、Channel 和 Buffer 的关系图的说明:
- 每个 Channel 都会对应一个 Buffer
- Selector 对应一个线程,一个线程对应多个 Channel(可以理解为连接)
- 该图反映了有三个 Channel 注册到该 Selector
- 程序切换到哪个 Channel 是由事件决定的,Event 就是一个重要的概念
- Selector 会根据不同的事件,在各个通道上切换
- Buffer 就是一个内存块,底层有一个数组
- 数据的读取写入是通过 Buffer,这个和 BIO 有所不同。BIO 中要么是输入流,或者是输出流(不能双向)。但是 NIO 的 Buffer 是可以读也可以写的,需要 flip 方法切换
- Channel 是双向的,可以反映底层操作系统的情况,比如 Linux,底层的操作系统通道就是双向的
缓冲区(Buffer)
基本 介绍
缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成一个 容器对象(含数组),该对象提供 一组方法,可以更轻松地使用内存块,缓冲区对象内置一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer,如下图所示:
在 NIO 中,Buffer 是一个顶层的父类,它是一个抽象类,常用的 Buffer 子类:
- ByteBuffer:存储字节数据到缓冲区
- ShortBuffer:存储字符串数据到缓冲区
- CharBuffer:存储字符数据到缓冲区
- IntBuffer:存储整数数据到缓冲区
- LongBuffer:存储长整型数据到缓冲区
- DoubleBuffer:存储小数到缓冲区
- FloatBuffer:存储小数到缓冲区
Buffer 类及其子类
Buffer 类定义了所有的缓冲区都具有的四个属性来提供关于其所包含的数据元素
属性 | 描述 |
---|---|
Capacity | 容量,即可以容纳的最大数据量,在缓冲区创建时被设定并且不能改变 |
Limit | 表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的 |
Position | 位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变该值,为下次读写做准备 |
Mark | 标记 |
Buffer 的读写切换
Buffer 的读写切换其实就是上面的四个属性来完成的,通过使用 Buffer 的 flip() 方法切换读或写的状态
通道(Channel)
NIO 的通道类似于流,但是有些区别如下:
- 通道可以同时进行读写,而流只能读或者只能写
- 通道可以实现异步读写数据
- 通道可以从缓冲读取数据,也可以写数据到缓冲
基本介绍
- BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)是双向的,可以读操作,也可以写操作
- Channel 在 NIO 中是一个接口 public interface Channel extens Closeable {}
- 常用的 Channel 类有:FileChannel、DatagramChannel、ServerSocketChannel 【类似 ServerSocket】和 SocketChannel 【类似 Socket】
- FileChannel 用于文件的数据读写,DatagramChannel 用于 UDP 的数据读写,ServerSocketChannel 和 SocketChannel 用于 TCP 的数据读写
选择器(Selector)
基本介绍
- Java 的 NIO,用非阻塞的 IO 方式,可以用一个线程,处理多个的客户端连接,就会使用到 Selector (选择器)
- Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个 Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通过,也就是管理多个连接和请求
- 只有在连接真正有读写事件发生时,才会进行读写,就大大地减少了系统的开销,并且不必为每个连接都创建一个线程,不用去维护多个线程
- 避免了多线程之间的上下文切换导致的开销
特点
- Netty 的 IO 线程 NioEventLoop 聚合了 Selector(选择器,也叫多路复用),可以同时并发处理成百上千个客户端连接
- 当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其它的任务
- 线程通常将非阻塞 IO 的空闲时间用于在其它通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道
- 由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起
- 一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统的同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大地提升
注意事项
- NIO 中的 SercerSocketChannel 功能类似于 ServerSocket,SocketChannel 功能类似于 Socket
Selector 相关方法说明:
- selector.select() // 阻塞
- selector.select(1000) // 阻塞 1000 毫秒,在 1000 毫秒后返回
- selector.wakeup() // 唤醒 selector
- selector.selectNow() // 不阻塞,立马返回
write(ByteBuffer src)
举例(写入数据):
package nettydemo.nio;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/** * @author Woo_home * @create by 2020/4/10 15:13 */
public class NIOFileChannelDemo {
public static void main(String[] args) throws Exception {
String str = "Hello Netty";
// 创建一个输出流 -> Channel
FileOutputStream fileOutputStream = new FileOutputStream("D:\\demo.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();
}
}
运行该程序会在 D 盘下生成一个文件,并把我们定义的内容写入到该文件中,如下:
我们可以通过 Debug 看下数据到底有没有写入到缓冲区,在这一行打个断点
注意看下下面的这些变量,这时候还没有 byteBuffer
进入到下一行,可以发现,多了个 byteBuffer 的变量
在上面的程序中我们设置了字节数组为 1024 大小的,正是 hb 中的,等于 0 表示该字节数组中还没有任何的数据写入
当我们把 String 的内容写入到缓冲区的时候,数组中就有数据了
从零的下标开始写入,写到为 10 的下标,为什么下标为 11 没有数据了呢?因为定义的 str 里面只有 11 个字节
read(ByteBuffer dst)
举例(读取数据)
package nettydemo.nio;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/** * @author Woo_home * @create by 2020/4/10 15:47 */
public class NIOFileChannelDemo02 {
public static void main(String[] args) throws Exception {
// 创建文件的输入流
File file = new File("D:\\demo.txt");
FileInputStream fileInputStream = new FileInputStream(file);
// 通过 fileInputStream 获取对应的 FileChannel -> 实际类型 FileChannelImpl
FileChannel fileChannel = fileInputStream.getChannel();
// 创建缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
// 将通道的数据读入到 byteBuffer
fileChannel.read(byteBuffer);
// 将缓冲区的字节转换成 String 类型
System.out.println(new String(byteBuffer.array()));
// 关闭流
fileInputStream.close();
}
}
输出:
举例(拷贝文件)
ByteBuffer
package nettydemo.nio;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/** * @author Woo_home * @create by 2020/4/10 15:58 */
public class NIOFileChannelDemo03 {
public static void main(String[] args) throws Exception {
FileInputStream fileInputStream = new FileInputStream("D:\\demo.txt");
FileChannel fileInputStreamChannel = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("demo.txt");
FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
while (true) { // 循环读取
// 这里有一个重要的操作,一定不要忘了
byteBuffer.clear(); // 清空 byteBuffer
int read = fileInputStreamChannel.read(byteBuffer);
if (read == -1) { // 表示读完
break;
}
// 将 byteBuffer 中的数据写入到 fileOutputStreamChannel -> demo.txt
byteBuffer.flip();
fileOutputStreamChannel.write(byteBuffer);
}
// 关闭流
fileInputStream.close();
fileOutputStream.close();
}
}
transferFrom
使用 FileChannel 的 transferFrom() 方法也能进行文件的拷贝
package nettydemo.nio;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
/** * @author Woo_home * @create by 2020/4/10 17:27 */
public class NIOTransferDemo {
public static void main(String[] args) throws Exception {
// 创建相关的流
FileInputStream fileInputStream = new FileInputStream("D:\\demo.txt");
FileOutputStream fileOutputStream = new FileOutputStream("demo.txt");
// 获取各个流对应的 fileChannel
FileChannel fileInputStreamChannel = fileInputStream.getChannel();
FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
// 使用 transferForm 完成拷贝
fileOutputStreamChannel.transferFrom(fileInputStreamChannel, 0, fileInputStreamChannel.size());
// 关闭通道和流
fileInputStreamChannel.close();
fileOutputStreamChannel.close();
fileInputStream.close();
fileOutputStream.close();
}
}
关于 Buffer 和 Channel 的注意事项和细节
- ByteBuffer 支持类型化的 put 和 get 操作,put 放入的是什么数据类型,get 就应该使用相应的数据类型来取出,否则可能有 BufferUnderflowException 异常
- 可以将一个普通的 Buffer 转换成只读 Buffer
- NIO 还提供了 MappedByteBuffer,可以让文件直接在内存(堆外的内存)中进行修改,而如何同步到文件由 NIO 来完成
- NIO 支持通过多个 Buffer(即 Buffer 数组)完成读写操作,即 Scattering 和 Gatering
还没有评论,来说两句吧...