没吃透Netty 缓冲区,还能算得上Java老司机?
前言
Java NIO 需要理解的主要有缓冲区,通道,选择器,这三个主要的部分。
基础
用户空间和内核空间
操作系统为了提供稳定性,把虚拟地址空间分为用户空间和内核空间,其中用户进程只能操作用户空间的内容,而内核空间的内容可以操作用户空间的内容以及用户空间的内容。
I/O过程中的数据流向
假设我们需要从磁盘中的某个文件读取数据,进程发起read系统调用,进入内核状态,内核会随即向磁盘控制硬件发出命令,要求其从磁盘读取数据,磁盘控制器把数据直接写入到内核缓冲区中,随后内核会吧数据从内核空间的临时缓冲区拷贝到用户缓冲区,进程再次切换回用户态继续执行。总结数据流向是:磁盘 -> 内核缓冲区 -> 用户缓冲区
内存空间多重映射
对于虚拟地址的空间,一个以上的虚拟地址可以指向同一个物理内存地址。如果用户空间的虚拟地址和内核空间的虚拟地址映射到同一个物理地址,那么这块物理地址代表的空间就对内核和用户进程都可见。便可省去数据在内核缓冲区和用户缓冲区来回复制的开销。
缓冲区
Java NIO 数据传输过程,数据先放到发送缓冲区 -> 通过通道发送到接收端 -> 接受端通道接受数据并填充到接受缓冲区 所以缓冲区的作用其实是连接通道作为数据传输的目标或者来源。
核心概念
属性
需要理解Buffer工作机制,需要了解如下几个属性
容量: 缓冲区的容量,创建缓冲区时指定
位置: 下一个要被读取或者写入元素的索引
上界: 缓冲区中第一个不能被读或者写的位置。
mark标记,一个备忘的位置
存取
缓冲区的核心就在于存取操作,buffer提供了相对位置存取和绝对位置存取两种方式。
相对位置存取:在当前的位置写入或者读取数据,然后增加位置的值。
绝对位置存取。在指定的位置的写入或者读取数据,不改变位置的值
代码如下
//相对位置存取
public abstract ByteBuffer put(byte b);
public abstract byte get();
//绝对位置存取
public abstract ByteBuffer put(int index, byte b);
public abstract byte get(int index);
翻转
翻转是 buffer的核心概念,可以理解buffer有两种模式,写模式和读模式。写模式:我们分配一个缓冲区,然后直接填充数据,读模式下。我们从头开始读取数据。如何从写模式切换到读模式,翻转,翻转的时候我们用limit记录待读取数据的长度,然后把位置置换为0就可以开始读取数据了。
public final Buffer flip() {
//记录待读取数据的长度
limit = position;
//从头开始读取数据
position = 0;
mark = -1;
return this;
}
demo
//创建一个缓冲区
ByteBuffer buffer = ByteBuffer.allocate(100);
//写数据
for (char c : "hello".toCharArray()) {
buffer.put((byte) c);
}
//翻转
buffer.flip();//等价于 buffer.limit(buffer.position()).position(0);
//读数据
while (buffer.hasRemaining()) {
char c = (char) buffer.get();
System.out.println(c);
}
直接缓冲区
对于一般的I/O过程,数据流向是,磁盘或者网络 -> 内核临时缓冲区 -> 用户空间缓冲区
直接缓冲区解决的是内核空间临时缓冲区到用户空间缓冲区复制这一步耗费的多余。虽然直接缓冲区是I/O的最佳选择,但是其比创建非直接缓冲区将会耗费更大的成本了,所以一般都是直接重复使用。
创建缓冲区
Buffer不能直接通过构造函数实例化,都是通过静态工厂方法创建,下为ByteBuffer的静态工厂方法。
//创建内存缓冲区
public static ByteBuffer allocate(int capacity);
//创建直接缓冲区