学习笔记,个人理解,如有错误劳烦指正,谢谢!
Buffer
(缓冲区)作为java nio
的三大核心组件之一 ,担任和数据直接打交道的任务,任何数据需要传输都必须储存在Buffer
中。它本质上是一块可以写入数据,然后可以从中读取数据的内存,并提供了一系列方法,用来方便的访问该块内存。
Buffer的分类
Buffer
是一个用于储存特定基本类型数据的容器,为了应对各种数据类型,java.nio.*
中为它提供了多种(包括但不限于以下七个)实现类用于扩展它的功能。
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
这些Buffer
类型代表了不同的数据类型。换句话说,就是可以通过char
,short
,int
,long
,float
或 double
类型来操作缓冲区中的数据,其中ByteBuffer
是最常用实现类。
Buffer的核心属性
查看Buffer
的源码,发现有如下四个属性。
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
对于新创建的Buffer
对象实例,四个属性的大小关系:mark <= position <= limit <= capacity
。
Buffer
提供了以上四个核心属性,以方便操作所包含的数据。
- capacity(
容量
)- 缓冲区能够容纳的数据元素的最大数量。容量在缓冲区刚创建时被设定,其不能为负并且不能改变。(不能被改变的原因是因为底层是数组)
- limit(
上限
)- 缓冲区里的数据的总数,代表了当前缓冲区中总共储存了多少数据。
- position(
位置
)- 下一个要被读或写的元素的位置。
position
会自动由相应的get( )
和 `put( )方法更新。
- 下一个要被读或写的元素的位置。
- mark(
标记
)- 执行mark()操作时,记录当前的读写位置,相当于一个存档,初始值为
-1
。
- 执行mark()操作时,记录当前的读写位置,相当于一个存档,初始值为
Buffer的基本用法
使用Buffer
读写数据一般遵循以下五个步骤:
- 创建一个指定大小的
Buffer
- 向
Buffer
中写入数据 - 调用
flip()
方法,由写模式转换为读模式 - 从
Buffer
中读取数据 - 调用
clear()
或者compact()
方法
代码演示
下面展示一下Buffer的读写过程,以及四个核心属性在读写过程中的变化
package cn.lincain.nio.buffer;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
public class BufferDemo {
public static void main(String[] args) throws Exception {
// 1. 创建一个容量为20的buffer
ByteBuffer buffer = ByteBuffer.allocate(20);
// 通过反射获取ByteBuffer下私有属性
Field field = ByteBuffer.class.getSuperclass().getDeclaredField("mark");
field.setAccessible(true);
// 新创建时,4个核心变量的值
System.out.println("初始时-->limit--->"+buffer.limit());
System.out.println("初始时-->position--->"+buffer.position());
System.out.println("初始时-->capacity--->"+buffer.capacity());
System.out.println("标记前-->mark--->" + (Integer) field.get(buffer));
System.out.println("标记时-->mark--->" + buffer.mark());
System.out.println("标记后-->mark--->" + (Integer) field.get(buffer));
System.out.println("--------------------------------------");
// 2 .添加一些数据到缓冲区中
String s = "byteBuffer";
buffer.put(s.getBytes());
// 写入数据后,4个核心变量的值
System.out.println("put完之后-->limit--->"+buffer.limit());
System.out.println("put完之后-->position--->"+buffer.position());
System.out.println("put完之后-->capacity--->"+buffer.capacity());
System.out.println("标记前-->mark--->" + (Integer) field.get(buffer));
System.out.println("标记时-->mark--->" + buffer.mark());
System.out.println("标记后-->mark--->" + (Integer) field.get(buffer));
System.out.println("--------------------------------------");
// 3 .调用flip()方法,由写模式转换为读模式
buffer.flip();
// 转换状态后,4个核心变量的值
System.out.println("flip完之后-->limit--->"+buffer.limit());
System.out.println("flip完之后-->position--->"+buffer.position());
System.out.println("flip完之后-->capacity--->"+buffer.capacity());
System.out.println("标记前-->mark--->" + (Integer) field.get(buffer));
System.out.println("标记时-->mark--->" + buffer.mark());
System.out.println("标记后-->mark--->" + (Integer) field.get(buffer));
System.out.println("--------------------------------------");
// 4. 从缓冲区中读取一个数据
char ch = (char) buffer.get();
System.out.println("读取的数据:" + ch);
// 从缓冲区读取一个数据后,4个核心变量的值
System.out.println("get完之后-->limit--->"+buffer.limit());
System.out.println("get完之后-->position--->"+buffer.position());
System.out.println("get完之后-->capacity--->"+buffer.capacity());
System.out.println("标记前-->mark--->" + (Integer) field.get(buffer));
System.out.println("标记时-->mark--->" + buffer.mark());
System.out.println("标记后-->mark--->" + (Integer) field.get(buffer));
System.out.println("--------------------------------------");
// 5.执行clear()方法
buffer.clear();
System.out.println("clear完之后-->limit--->"+buffer.limit());
System.out.println("clear完之后-->position--->"+buffer.position());
System.out.println("clear完之后-->capacity--->"+buffer.capacity());
System.out.println("标记前-->mark--->" + (Integer) field.get(buffer));
System.out.println("标记时-->mark--->" + buffer.mark());
System.out.println("标记后-->mark--->" + (Integer) field.get(buffer));
System.out.println("--------------------------------------");
// 遍历buffer,其中的数据依然存在,并没有真正的删除
for (int i = 0; i < 10; i++) {
System.out.print((char)buffer.get(i));
}
}
}
执行以上代码,得到的结果如下:
初始时-->limit--->20
初始时-->position--->0
初始时-->capacity--->20
标记前-->mark--->-1
标记时-->mark--->java.nio.HeapByteBuffer[pos=0 lim=20 cap=20]
标记后-->mark--->0
--------------------------------------
put完之后-->limit--->20
put完之后-->position--->10
put完之后-->capacity--->20
标记前-->mark--->0
标记时-->mark--->java.nio.HeapByteBuffer[pos=10 lim=20 cap=20]
标记后-->mark--->10
--------------------------------------
flip完之后-->limit--->10
flip完之后-->position--->0
flip完之后-->capacity--->20
标记前-->mark--->-1
标记时-->mark--->java.nio.HeapByteBuffer[pos=0 lim=10 cap=20]
标记后-->mark--->0
--------------------------------------
读取的数据:b
get完之后-->limit--->10
get完之后-->position--->1
get完之后-->capacity--->20
标记前-->mark--->0
标记时-->mark--->java.nio.HeapByteBuffer[pos=1 lim=10 cap=20]
标记后-->mark--->1
--------------------------------------
clear完之后-->limit--->20
clear完之后-->position--->0
clear完之后-->capacity--->20
标记前-->mark--->-1
标记时-->mark--->java.nio.HeapByteBuffer[pos=0 lim=20 cap=20]
标记后-->mark--->0
--------------------------------------
byteBuffer
结论
根据上述结果,得出如下结论:
- 创建
buffer
实例对象后,capcity
的值将不会改变。 put
和get
操作会自动更新position
的值flip
和clear
操作会将position
归零,以及将mark
归为-1
put
和clear
操作不会删除buffer
中的数据mark
记录的是调用mark()
方法时,当前position
的值,方便后续操作
上面的代码没有对compact()
方法进行演示,单独说一下compact
操作会将还未读取的数据复制到buffer
的头部,后面的数据保持不变,以上面的btyeBuffer
为例,如果get()
了一个字符后,执行compact()
方法,buffer中的数据就变成了tyeBufferr
,数据的总量并没有发生变化。