【NIO】Buffer 详解
简介:
一个用于存储 基本数据类型的容器
由 java.nio 包定义的,所有 缓冲区 都是 Buffer抽象类 的 子类
Java NIO 中的 Buffer 主要用于 与 NIO 通道进行交互
数据 是从 通道(Channel) 读入 缓冲区(Buffer),从 缓冲区(Buffer) 写入 通道(Channel) 中的
Buffer 就像一个 数组,可以保存多个 相同类型的数据
继承关系:
现在,本人来介绍下Buffer 抽象类的 常用子类:
- ByteBuffer —— 存储byte类型的缓冲区
- CharBuffer —— 存储char类型的缓冲区
- ShortBuffer —— 存储short类型的缓冲区
- IntBuffer —— 存储int类型的缓冲区
- LongBuffer —— 存储long类型的缓冲区
- FloatBuffer —— 存储float类型的缓冲区
- DoubleBuffer —— 存储double类型的缓冲区
现在,本人来讲解下 操作Buffer 的 核心API:
核心API:
方法名 | 功能 |
---|---|
allocate(int capacity) | 申请一块指定capacity大小的 非直接内存缓冲区空间 |
allocateDirect(int capacity) | 申请一块指定capacity大小的 直接内存缓冲区空间 |
put() | 存入数据到缓冲区中 |
get() | 获取缓冲区中的数据 |
flip() | 切换读取数据模式 |
rewind() | 可重复读 |
clear() | 清空缓冲区. 但是缓冲区中的数据依然存在,但是处于“被遗忘”状态 |
mark() | 标记是一个索引,方便后面使用reset()方法跳回这标记处 |
reset() | 使position恢复到之前用mark()方法标记的 |
除此之外,还有一个方法十分重要:
FileChannel抽象类 的 map()方法:
它的 作用,是 :
将 文件区域 直接映射到 内存 中来操作
(操作该对象,也相当于 操作 直接内存空间)
上述 几个Buffer,都采用 相同的实现原理 进行数据管理,只是各自管理的 数据类型不同 而已
那么,本人就以 最常用的 ByteBuffer
和 CharBuffer
,来大致讲解下 Buffer
的 实现原理:
源码剖析:
不同数据的存储 实现:
首先,我们来看看 ByteBuffer
和 CharBuffer
的 部分源码:
我们可以看到:
- Buffer实现类 的 构造方法,我们API调用者,是无法调用的
因此,我们若是想 使用Buffer存储数据,就需要调用其 静态方法allocate 去 申请 缓冲区对象存储空间- 不同的Buffer实现类,其 数据存储类型 是 其相应类型的数组
这就是 不同缓冲区,能够实现 不同数据存储 的 原理
在上文中,我们了解了 不同数据的存储原理
那么,缓冲区的基本功能,是如何实现的呢?
“缓冲区”基本功能 实现:
由于本人在上文中,已经明确说明:
所有
NIO
提供的 缓冲区实现,都 继承自Buffer抽象类
因此,“缓冲区”基本功能 的实现,一定是在 Buffer抽象类
中
那么,我们来看下 Buffer抽象类
的 部分核心源码:
我们可以看到:
Buffer抽象类
中,有几个 成员属性
那么,这些 Buffer的 基本属性,就是 “缓冲区”基本功能 的 实现原理
现在,本人来讲解下 Buffer
的 基本属性:
基本属性:
概念:
容量 (capacity)
:
表示Buffer
的 最大数据容量
capacity 不能为负,并且 创建后不能更改限制 (limit)
:
第一个不应该 读取或写入的数据的 索引,即 位于 limit 后 的数据 不可读写
limit 不能为负,并且 不能大于其容量位置 (position)
:
下一个 要 读取或写入 的 数据 的 索引
position 不能为负,并且 不能大于其限制标记 (mark)
与重置 (reset)
:
mark是一个 索引,
通过 Buffer 中的 mark()方法 指定 Buffer 中一个 特定的 position ,之后可以通过调用 reset()方法 使得 当前读/写指针 重置 到这个 特定position
属性关系
以上几个基本属性,遵循如下 恒定式:
$$
0 <= mark <= position <= limit <= capacity
$$
那么,本人现在通过几张图,来展示下 调用上述API,会发生如何现象:
属性变化 图解:
现在,本人来展示下 有关基本属性 的 API:
相关API:
方法名 | 功能 |
---|---|
clear() | 清空缓冲区 并 返回对缓冲区的引用 |
flip() | 将 缓冲区的界限,设置为 当前位置,并将 当前位置 重置为0 |
capacity() | 返回 当前Buffer 的 capacity属性大小 |
hasRemaining() | 判断 缓冲区中 是否还有元素 |
limit() | 返回 当前Buffer 的 limit属性的值 |
limit(int n) | 将 当前Buffer 的 limit属性 设置为 n 并 返回 一个 具有 新limit 的 Buffer对象 |
mark() | 将 目标缓冲区 的 mark属性,设置为 当前位置position的值 |
position() | 返回 缓冲区 的 position属性 |
position(int n) | 将 缓冲区 的 position属性 设置为 n,并 返回 修改后的Buffer对象 |
remaining() | 返回 position属性的值 和 limit属性的值,中间的元素个数 |
reset() | 将 position属性 设置为 mark属性 的值 |
rewind() | 将 position属性 设为为 0,将 mark属性 重置为 -1 |
使用展示:
那么,现在,本人来通过一个例子来展示下这些API 的使用:
package edu.youzg.about_nio.core;
import java.nio.ByteBuffer;
public class Test {
public static void main(String[] args) {
String str="youzhuange";
//申请10字节缓冲区空间
System.out.println("--------------allocate(10)-------");
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
int capacity = byteBuffer.capacity();
int position = byteBuffer.position();
int limit = byteBuffer.limit();
System.out.println("capacity:"+capacity);
System.out.println("position:"+position);
System.out.println("limit:"+limit);
//往容器中放数据 put();
System.out.println("----------put()-----------");
byteBuffer.put(str.getBytes());
capacity = byteBuffer.capacity();
position = byteBuffer.position();
limit = byteBuffer.limit();
System.out.println("capacity:" + capacity);
System.out.println("position:" + position);
System.out.println("limit:" + limit);
//读取缓冲区中的数据,切换成读取模式
System.out.println("---------flip()--------");
byteBuffer.flip();
capacity = byteBuffer.capacity();
position = byteBuffer.position();
limit = byteBuffer.limit();
System.out.println("capacity:" + capacity);
System.out.println("position:" + position);
System.out.println("limit:" + limit);
//读取数据 get()
System.out.println("---------get()---------");
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
System.out.println(new String(bytes, 0, byteBuffer.limit()));
capacity = byteBuffer.capacity();
position = byteBuffer.position();
limit = byteBuffer.limit();
System.out.println("capacity:" + capacity);
System.out.println("position:" + position);
System.out.println("limit:" + limit);
//可重复读取
System.out.println("---------rewind()---------");
byteBuffer.rewind();
capacity = byteBuffer.capacity();
position = byteBuffer.position();
limit = byteBuffer.limit();
System.out.println("capacity:" + capacity);
System.out.println("position:" + position);
System.out.println("limit:" + limit);
//标记 mark
System.out.println("---------mark()--------");
byteBuffer.mark();
byteBuffer.get(bytes,2,2);
System.out.println(byteBuffer.position());
//回到上一次标记position的位置 使用reset();就可以回到上一次标记的位置
System.out.println("---------reset()--------");
byteBuffer.reset();
System.out.println(byteBuffer.position());
//查询还有没有可读数据
System.out.println("---------remaining()--------");
if(byteBuffer.hasRemaining()){
System.out.println(byteBuffer.remaining()); //remaining()还有多少可读取数据
}
//清空缓冲区
System.out.println("--------clear()----------------");
//clear()并不是把缓冲区里面的字节数据清掉,而是把这些指针,设置到初始状态
byteBuffer.clear();
capacity = byteBuffer.capacity();
position = byteBuffer.position();
limit = byteBuffer.limit();
System.out.println("capacity:" + capacity);
System.out.println("position:" + position);
System.out.println("limit:" + limit);
byte b = byteBuffer.get();
System.out.println((char)b);
}
}
本人再来展示下 运行结果:
运行结果:
NIO
相较于 BIO
,数据读写效率 很 高
但是,在 Buffer
层面,是如何 实现 的呢?
答曰:
直接内存 的使用,以及 零拷贝 概念的引入
那么,本人在这里,就来讲解下 直接内存 与 零拷贝 的概念和原理:
直接内存 与 零拷贝:
请观看本人博文 —— 《【NIO】直接内存 与 零拷贝 详解》
那么,至此,NIO
的 Buffer
组件,就讲解完毕了!