这里演示的是CharBuffer,ByteBuffer等同理
- 首先讲Buffer的存储方式:
buffer中有四个参数:
1) mark:作position定位用,后面会讲使用;
2)position:缓冲内数据定位,起始为0;
3)limit:有效数据位界限,起始为capacity:
4)capacity:缓冲总大小界限。
【使用原理】
a) 初始化,给定总容量,position=0, limit=capacity
b) 当使用put方法存入数据是,通过position来记录存储的容量变化,position不断后移,直到存储结束(写完成)
c)写完成需要调用flip方法刷新,limit=position,position=0
保障limit记录的是可读写区域的大小,position已读部分重置为空
d) 读数据直到读完成,需要调用clear方法,position=0, limit=capacity
import java.nio.CharBuffer;
public class Buffer {
public static void main(String[] args) {
CharBuffer buffer = CharBuffer.allocate(8);
System.out.println("--------初始化---------");
System.out.println("这是capacity:"+buffer.capacity());
System.out.println("这是limit:"+buffer.limit());
System.out.println("这是position:"+buffer.position());
buffer.put('h');
buffer.put('e');
System.out.println("--------put后---------");
System.out.println("这是capacity:"+buffer.capacity());
System.out.println("这是limit:"+buffer.limit());
System.out.println("这是position:"+buffer.position());
buffer.flip();
System.out.println("--------flip后---------");
System.out.println("这是capacity:"+buffer.capacity());
System.out.println("这是limit:"+buffer.limit());
System.out.println("这是position:"+buffer.position());
System.out.println("--------get1后---------");
System.out.println(buffer.get());
System.out.println("这是capacity:"+buffer.capacity());
System.out.println("这是limit:"+buffer.limit());
System.out.println("这是position:"+buffer.position());
}
}
结果如下:
接下来说mark的用法:
//此时标记获取一个char后position位置
buffer.mark();
System.out.println("--------get2后---------");
System.out.println(buffer.get());
System.out.println("这是capacity:"+buffer.capacity());
System.out.println("这是limit:"+buffer.limit());
System.out.println("这是position:"+buffer.position());
//重置position为上一标记点
buffer.reset();
System.out.println("--------reset后---------");
System.out.println("这是capacity:"+buffer.capacity());
System.out.println("这是limit:"+buffer.limit());
System.out.println("这是position:"+buffer.position());
reset后position位置重置为上一mark标记点
原理是,调用mark方法时,将position值传给mark,调用reset时再将mark值传给position
这是mark和reset的源码:
public final Buffer mark() {
mark = position;
return this;
}
/**
* Resets this buffer's position to the previously-marked position.
*
* <p> Invoking this method neither changes nor discards the mark's
* value. </p>
*
* @return This buffer
*
* @throws InvalidMarkException
* If the mark has not been set
*/
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
注: put值后一定要flip刷新,否则会读取到null值或者文件格式不支持;
clear方法旨在清除原有数据标记相当于再次初始化
buffer.clear();
System.out.println("--------clear后---------");
System.out.println("这是capacity:"+buffer.capacity());
System.out.println("这是limit:"+buffer.limit());
System.out.println("这是position:"+buffer.position());
System.out.println(buffer.get());
Socket的write和read操作对于Buffer的影响
对于buffer来说,Socket的write操作和read操作都是对buffer进行put/get操作,如:
ByteBuffer write = ByteBuffer.allocate(128);
write.put("hello client , i am server!".getBytes());
System.out.println("------------- server flip之前");
System.out.println("position:"+write.position());
System.out.println("limit:"+write.limit());
System.out.println("capacity:"+write.capacity());
write.flip();
System.out.println("------------- server flip之后");
System.out.println("position:"+write.position());
System.out.println("limit:"+write.limit());
System.out.println("capacity:"+write.capacity());
socket.write(write);
System.out.println("------------ server -write之后-------------");
System.out.println("position:"+write.position());
System.out.println("limit:"+write.limit());
System.out.println("capacity:"+write.capacity());
这里是向Socket中写入ByteBuffer,我们先向buffer中put进了数据,之后将buffer flip刷新,切换到预备读状态,socket写操作之后,position变为了27,为有效数据的长度,limit未变。
ByteBuffer read = ByteBuffer.allocate(128);
System.out.println("-------------read之前");
System.out.println("position:"+read.position());
System.out.println("limit:"+read.limit());
System.out.println("capacity:"+read.capacity());
socketChannel.read(read);
System.out.println("-------------read之后---flip之前");
System.out.println("position:"+read.position());
System.out.println("limit:"+read.limit());
System.out.println("capacity:"+read.capacity());
read.flip();
System.out.println("-------------read之后---flip之后");
System.out.println("position:"+read.position());
System.out.println("limit:"+read.limit());
System.out.println("capacity:"+read.capacity());
这里是Socket中数据读入ByteBuffer,socket读操作之后,position变为了27,为有效数据的长度,limit未变。
由此可见,Socket进行write或者read时都是对buffer进行了put/get操作
write底层源码如下:
private static int writeFromNativeBuffer(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
//这块是write进的position值,因为经过了flip,所以为0
int var5 = var1.position();
//这块是limit值,27
int var6 = var1.limit();
assert var5 <= var6;
int var7 = var5 <= var6 ? var6 - var5 : 0;
boolean var8 = false;
if (var7 == 0) {
return 0;
} else {
int var9;
//var2是 是否使用了reset,否为-1.
if (var2 != -1L) {
var9 = var4.pwrite(var0, ((DirectBuffer)var1).address() + (long)var5, var7, var2);
} else {
//这里调用底层c进行写入,sun.nio.ch.NativeDispatcher#write,真正的写入数据,第一个参数是内存中的偏移量,第二个参数是写入数据的大小
var9 = var4.write(var0, ((DirectBuffer)var1).address() + (long)var5, var7);
}
if (var9 > 0) {
var1.position(var5 + var9);
}
return var9;
}
}
从上面的代码看,最终就是调用到了sun.nio.ch.NativeDispatcher#write方法,在调用flip之后var5是0,var7是4,此时就是在堆外分配的内存地址address+0的基础上偏移4个字节,正好是"hehe"这个字符串,而在调用flip之前,var5是4, var7是1020,调用write方法是是在address+4的基础上再偏移1020个字节,结合上面put的解释,这段内存空间根本就没有数据。这就解释了为什么调用fileChannel.write之前要掉用flip方法。
参考文章:https://zhuanlan.zhihu.com/p/439380628?utm_id=0