Buffer
Buffer
Mark<=Position <=Limt<=Capacity
状态变量
- position:
在从通道读取时,将所读取的数据放到底层的数组中。 position 变量跟踪已经写了多少数据。它指定了下一个字节将放到数组的哪一个元素中。因此,如果从通道中读三个字节到缓冲区中,那么缓冲区的position 将会设置为3,指向数组中第四个元素。
- mark:
一个备忘标记位置调用,mark()函数设置mark=positon,调用reset()设置position=mark。
- limit
limit 变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
- capacity
缓冲区的 capacity 表明可以储存在缓冲区中的最大数据容量。实际上,它指定了底层数组的大小或者指定了准许使用的底层数组的容量。
图形演示
初始:一个新创建的缓冲区。假设这个缓冲区的 总容量 为8个节。 Buffer 的状态如下所示:
- 读入缓冲区:写入5个字节。Buffer 的状态如下所示:
- flip():它将设置limit为它将设置position为0;它将设置position为0;可以读缓冲区的内容了:
- 写入通道
第一次写入时,从缓冲区中取四个字节并将它们写入输出通道。这使得 position 增加到
4,而 limit 不变:
再次写入,只剩下一个字节可写, limit在调用 flip() 时被设置为
5,并且 position 不能超过 limit。所以最后一次写入操作从缓冲区取出一个字节并将它写入输出通道。这使得 position 增加到
5,并保持 limit 不变。
- clear
它将limit设置为与capacity 相同;它设置 position为0;可以再次往缓冲区写数据了。
缓冲区操作
- 分配
/**
* 分配缓冲区
*/
@Test
public void allocateBuffer(){
ByteBuffer bf=ByteBuffer.allocate(1024);
System.out.println("position:"+bf.position());
System.out.println("limit:"+bf.limit());
System.out.println("capacity:"+bf.capacity());
}运行结果:
- 包装
/**
* 将原有数组包装成一个缓冲区。
*/
public void wrapBuffer(){
int [] ints={1,2,3,4,5};
IntBuffer ib=IntBuffer.wrap(ints);
ib.put(1,8);
System.out.println(Arrays.toString(ints));
}运行结果:
数组和缓冲区共用一分数据。
- 分片
@Test
public void sliceBuffer() {
IntBuffer ib = IntBuffer.allocate(10);
for(int i=0;i<10;i++){
ib.put(i);
}
ib.position(3);
ib.limit(6);
IntBuffer sliceBuf=ib.slice();
System.out.println("before slice:"+Arrays.toString(sliceBuf.array()));
for(int j=0;j<sliceBuf.capacity();j++){
sliceBuf.put(j,sliceBuf.get()*3);
}
System.out.println("after slice:"+Arrays.toString(sliceBuf.array()));
System.out.println("old buffer"+Arrays.toString(sliceBuf.array()));
}运行结果:
原缓冲区和新的缓冲区分片共享同一个底层数据数组,并且对缓冲区分片的新缓冲区修改只影响子缓冲区。
- 压缩
@Test
public void compactBuffer() {
CharBuffer cb = CharBuffer.allocate(15);
for (int i = 65; i < 75; i++) {
cb.put((char) i);
}
cb.flip();
System.out.println("original buffer:"+Arrays.toString(cb.array()));
System.out.println("original positon:"+cb.position());
System.out.println("original limit:"+cb.limit());
for(int j=0;j<6;j++){
cb.get();
}
System.out.println("after get position:"+cb.position());
cb.compact();
System.out.println("after compact position:"+cb.position());
System.out.println("after compact limit:"+cb.limit());
System.out.println("after compact buffer:"+Arrays.toString(cb.array()));
cb.flip();
System.out.println("after flip position:"+cb.position());
System.out.println("after flip limit:"+cb.limit());
}运行结果:
未读的元素移动到下表0开始,position为最后一个未读元素的下一个下标,limit在这个过程中没变化,如果想读取这部分元素,执行一次翻转。
- 比较
两个缓冲区相等的充分必要条件:
- 两个缓冲区类型必须相同
- 两个缓冲区剩余元素数量必须相同,两个缓冲区容量可以不同
3. 两个缓冲区通过get()取得的元素序列必须相同
@Test
public void compareBuffer(){
//cb1和cb2的capacity不相等
CharBuffer cb1=CharBuffer.allocate(10);
CharBuffer cb2=CharBuffer.allocate(8);
for (int i=65;i<75;i++){
cb1.put((char)i);
}
for (int j=69;j<75;j++){
cb2.put((char)j);
}
cb1.flip();
cb2.flip();
System.out.println("original cb1:"+Arrays.toString(cb1.array()));
System.out.println("original cb2:"+Arrays.toString(cb2.array()));
//cb1读取六个元素
for(int m=0;m<5;m++){
cb1.get();
}
//cb2读取一个元素
cb2.get();
System.out.println(cb1.equals(cb2));
}
运行结果:
只读缓冲区
public void readOnlyBuffer() {
IntBuffer ib = IntBuffer.allocate(10);
for (int i = 0; i < 10; i++) {
ib.put(i);
}
System.out.println("original buffer:"+Arrays.toString(ib.array()));
IntBuffer readOnlyBuffer=ib.asReadOnlyBuffer();
readOnlyBuffer.flip();
//以下语句会接收异常:ReadOnlyBufferException。
//System.out.println("readonly buffer:"+Arrays.toString(readOnlyBuffer.array()));
System.out.print("original readonly buffer:");
for (int j=0;j<readOnlyBuffer.capacity();j++){
System.out.print(readOnlyBuffer.get());}
}只读缓冲区会和原缓冲区公用一分数据。
直接缓冲区
给定一个直接字节缓冲区,Java 虚拟机将尽最大努力直接对它执行本机 I/O
操作。也就是说,它会在每一次调用底层操作系统的本机 I/O
操作之前(或之后),尝试避免将缓冲区的内容拷贝到一个中间缓冲区中(或者从一个中间缓冲区中拷贝数据)
/**
* 直接缓冲区
* @throws Exception
*/
@Test
public void directBuffer() throws Exception {
FileInputStream fi = new FileInputStream(this.sourcePath);
FileOutputStream fo = new FileOutputStream(this.destPath);
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
FileChannel fic = fi.getChannel();
FileChannel foc = fo.getChannel();
while (fic.read(directBuffer) != -1) {
directBuffer.flip();
foc.write(directBuffer);
directBuffer.clear();
}
fi.close();
fo.close();
}
内存映像
内存映射文件 I/O
是通过使文件中的数据出现为内存数组的内容来完成的。一般来说,只有文件中实际读取或者写入的部分才会送入(或者 映射 )到内存中。
只能通过FileChannel来创建。
代码实例:
public void mappedByteBuffer() throws Exception{
FileInputStream fi = new FileInputStream(this.sourcePath);
//MappedByteBuffer是ByteBuffer的子类。
MappedByteBuffer mappedByteBuffer=fi.getChannel().map(FileChannel.MapMode.READ_ONLY,0l,1024l);}
字节缓冲区
- 字节顺序
- 大端字节
2. 小端字节
- 取决于硬件设计,JVM默认是大端字节,IP协议使用大端的网络字节顺序。
- JVM对字节顺序的支持:ByteOrder
获取本地字节顺序:ByteOrder.nativeOrder();
/**
* 字节顺序
*/
@Test
public void byteOrder(){
System.out.println("My mac pro byte order:"+ByteOrder.nativeOrder());
}
运行结果:
视图缓冲区
/**
* 视图buffer
*/
@Test
public void viewBuffer() {
this.byteOrder();
ByteBuffer bb = ByteBuffer.allocate(16).order(ByteOrder.BIG_ENDIAN);
CharBuffer cb=bb.asCharBuffer();
bb.put((byte)0);
bb.put((byte)‘r‘);
bb.put((byte)0);
bb.put((byte)‘e‘);
bb.put((byte)0);
bb.put((byte)‘q‘);
bb.put((byte)0);
bb.put((byte)‘u‘);
bb.put((byte)0);
bb.put((byte)‘e‘);
bb.put((byte)0);
bb.put((byte)‘l‘);
bb.put((byte)0);
bb.put((byte)‘q‘);
bb.put((byte)0);
bb.put((byte)‘i‘);
System.out.println("original bb position:"+bb.position()+",limit:"+bb.limit()+",bb is:"+Arrays.toString(bb.array()));
System.out.println("view buffer cb position:"+cb.position()+",limit:"+cb.limit()+",bb is:"+cb.toString());
}
运行结果: