在本篇博文中,本人主要讲解NIO 的两个核心点 —— 缓冲区(Buffer) 和 通道 (Channel)之一的 缓冲区(Buffer),
有关NIO流的其他知识点请观看本人博文《详解 NIO流》
缓冲区(Buffer 类):
简介:
缓冲区( Buffer ):
一个用于特定基本数据类型的容器。
由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类。
Java NIO 中的 Buffer 主要用于与 NIO 通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的
Buffer 就像一个数组,可以保存多个相同类型的数据
那么,现在,本人来介绍下Buffer 抽象类的 常用子类:
ByteBuffer —— 存储byte类型的缓冲区
CharBuffer —— 存储char类型的缓冲区
ShortBuffer —— 存储short类型的缓冲区
IntBuffer —— 存储int类型的缓冲区
LongBuffer —— 存储long类型的缓冲区
FloatBuffer —— 存储float类型的缓冲区
DoubleBuffer —— 存储double类型的缓冲区上述 Buffer 类 他们都采用相似的方法进行管理数据,
只是各自管理的数据类型不同而已。
Buffer是没有构造方法的,
Buffer的对象都是通过 allocate(int capacity)方法 来获取的
现在,本人来讲解下如何获取 Buffer 抽象类 的 子类的对象:
- 手段1:
直接缓冲区:子类的allocateDirect()静态方法
非直接缓冲区:子类的allocate()静态方法
- 手段2:
直接缓冲区:通过 FileChannel 的 map() 方法 将文件区域直接映射到内存中来创建。(该方法返回MappedByteBuffer )
那么,本人现在来展示下处理缓冲区的核心API:
核心API:
put() | 存入数据到缓冲区中 |
get() | 获取缓冲区中的数据 |
flip() | 切换读取数据模式 |
rewind() | 可重复读 |
clear() | 清空缓冲区. 但是缓冲区中的数据依然存在,但是处于“被遗忘”状态 |
mark() | 标记是一个索引,方便后面使用reset()方法跳回这标记处 |
reset() | 使position恢复到之前用mark()方法标记的 |
现在,本人来讲解下Buffer的基本属性:
Buffer的基本属性:
Buffer 中的重要属性:
- 容量 (capacity) :
表示 Buffer 最大数据容量。
缓冲区容量不能为负,并且创建后不能更改。
- 限制 (limit) :
第一个不应该读取或写入的数据的索引,即位于 limit 后的数据不可读写。
缓冲区的限制不能为负,并且不能大于其容量。
- 位置 (position) :
下一个要读取或写入的数据的索引。
缓冲区的位置不能为负,并且不能大于其限制
- 标记 (mark) 与重置 (reset) :
标记是一个索引,
通过 Buffer 中的 mark() 方法 指定 Buffer 中一个特定的 position ,之后可以通过调用 reset() 方法恢复到这个 position.
标记、位置、限制、容量遵守以下不变式:
下图展示了处理缓冲区的API 会对缓冲区的属性的影响:
现在,本人来展示下Buffer类的API:
Buffer的API:
方法 | 描述 |
---|---|
Buffer clear() | 清空缓冲区并返回对缓冲区的引用 |
Buffer flip() | 将缓冲区的界限设置为当前位置,并将当前位置充值为0 |
int capacity() | 返回Buffer的capacity大小 |
boolean hasRemaining() | 判断缓冲区中是否还有元素 |
int limit() | 返回Buffer的界限(limit)的位置 |
Buffer limit(intn) | 将设置缓冲区界限为n,并返回一个具有新limit的缓冲区对象 |
Buffer mark() | 对缓冲区设置标记 |
int position() | 返回缓冲区的当前位置position |
Buffer position(int n) | 将设置缓冲区的当前位置为n,并返回修改后的Buffer对象 |
int remaining() | 返回position和limit之间的元素个数 |
Buffer reset() | 将位置position转到以前设置的mark所在的位置 |
Buffer rewind() | 将位置设为为0,取消设置的mark |
那么,现在,本人来通过一个例子来展示下这些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);
}
}
本人再来展示下运行结果:
那么,现在,本人来讲解缓冲区的分类 —— 直接缓冲区 与 非直接缓冲区:
字节缓冲区要么是直接的,要么是非直接的。
直接缓冲区是将 缓冲区建立在 物理内存中,非直接缓冲区是将缓冲区建立在JVM中
如果为直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。
也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),
虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。直接字节缓冲区可以通过调用此类的 allocateDirect() 工厂方法 来创建。
此方法返回的 缓冲区进行分配和取消分配所需成本通常高于 非直接缓冲区 。
直接缓冲区的内容 可以驻留在常规的垃圾回收堆之外,
因此,它们对应用程序的内存需求量造成的影响可能并不明显。
所以,建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。
一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。直接字节缓冲区还可以通过 FileChannel 的 map() 方法 将文件区域直接映射到内存中来创建。
该方法返回MappedByteBuffer 。
Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。
如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,
则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。字节缓冲区是直接缓冲区还是非直接缓冲区 可通过调用其 isDirect() 方法来确定。
提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理。
现在,本人来通过两张图来展示下这两种缓冲区的底层传输步骤:
至于这两种缓冲区的使用,请观看本人博文 ——《详解 通道 (Channel 接口)》
现在,本人对上图中的内存映射文件为什么高效率做下解释:
内存映射文件效率为什么高:
文件I/O的读操作,
会先向文件设备发起读请求,驱动把请求要读的数据读取到文件的缓冲区中(这个缓冲区位于内核),
然后再把这个缓冲区中的数据复制到程序虚拟地址空间中的一块区域中。文件I/O的写操作,
会向文件设备发起写请求,驱动把要写入的数据复制到程序的缓冲区中(位于用户空间),
然后再把这个缓冲区的数据复制到文件的缓冲区中。
内存映射文件,是把位于硬盘中的文件看做是程序地址空间中一块区域对应的物理存储器,
文件的数据就是这块区域内存中对应的数据,读写文件中的数据,直接对这块区域的地址操作 就可以,
减少了内存复制的环节。所以说,内存映射文件比起文件I/O操作,效率要高,而且文件越大,体现出来的差距越大。
那么,有关 缓冲区(Buffer) 的基本知识点到这里就讲解完成了!
若是感觉本篇博文内容有帮助的同学,请留下免费👍和 关注,谢谢!!!
(本人 NIO流 博文链接:https://blog.csdn.net/weixin_45238600/article/details/104292640)