在NIO中,数据的读写操作始终是与缓冲区相关联的,缓冲区是将数据移进移出通道的唯一方式。读取时通道(SocketChannel)将数据读入缓冲区,写入时首先要将发送的数据按顺序填入缓冲区。可以把通道比作为煤矿(数据区),而把缓冲区比如为运煤车,想要得到煤一般都通过运煤车来获取,而不是直接在煤矿取煤。

NIO系列-3: ByteBuffer的几个知识点_第1张图片

图1 - Buffer的类图结构

Buffer及其子类都不是线程安全的。每个Buffer都有以下的属性: 

1)capacity:这个Buffer最多能放多少数据。capacity一般在buffer被创建的时候指定。

2)limit:在Buffer上进行的读写操作都不能越过这个下标。当写数据到buffer中时,limit一般和capacity相等,当读数据时,limit代表buffer中有效数据的长度。

3)position:读/写操作的当前下标。当使用buffer的相对位置进行读/写操作时,读/写会从这个下标进行,并在操作完成后,buffer会更新下标的值。

4)mark:一个临时存放的位置下标。调用mark()会将mark设为当前的position的值,以后调用reset()会将position属性设 置为mark的值。mark的值总是小于等于position的值,如果将position的值设的比mark小,当前的mark值会被抛弃掉。

这些属性总是满足以下条件:0 <= mark <= position <= limit <= capacity

limit和position的值除了通过limit()和position()函数来设置,也可以通过下面这些函数来改变:

1)flip()

public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}

在向ByteBuffer写入数据后,position为缓冲区中刚刚读入的数据的最后一个字节的位置,flip方法将limit值置为position值,position置0,这样在调用get*()方法从ByteBuffer中取数据时就可以取到ByteBuffer中的有效数据。

记住,在读取Buffer中的数据前,必须先flip()!

2)clear()

public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}

clear()并未清空缓冲区中的数据,它只把position设为0,把limit设为capacity,一般在把数据写入Buffer前调用。

3)rewind()

public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}

把position设为0,limit不变,一般在把数据重写入Buffer前调用。

除了这几个方法外,还有几个方法也是必须要掌握的:

1)

public final int remaining() //一句代码return limit - position;
public final int hasRemaining() //一句代码return limit > position;

2)compact()

压缩数据。比如当前capacity是6,当前position指向2。(即0,1的数据已经被读出了,没用了),

那么compact方法将把2,3,4,5的数据挪到0,1,2,3的位置,然后position指向4的位置。这样的意思是,从4的位置接着再写入数据。

写完后,把position挪到0,再读出数据,然后再compact(),如此反复……

为了加深对ByteBuffer的理解,我下面再举一个例子:

import java.nio.ByteBuffer;
public class ByteBufferTest {
public static void main(String[] args) {
//10个字节大小
ByteBuffer buffer = ByteBuffer.allocate(10);
//capacity是10,limit是10,positon是0
System.out.print("Init ByteBuffer, capacity is:"+buffer.capacity());
System.out.print(" ,limit is:"+buffer.limit());
System.out.println(" ,position is:"+buffer.position());
//输出看看,输出是10个0
printBuffer(buffer);
//此时,position指向位置9,已经是最大容量了。
//把position挪回位置0
buffer.rewind();
//写操作,指针会自动移动
buffer.putChar('A');
System.out.println("position指向2: "+buffer.position()); //指针指向2
buffer.putChar('我');
System.out.println("position指向4: "+buffer.position()); //指针指向4
//flip()把limit设置为当前位置,position挪回位置0
//相当于下面两句:
//buffer.limit(4);
//buffer.position(0);
buffer.flip();
//输出前4个字节
printBuffer(buffer);
System.out.println("---- flip() end ----");
//positon挪到位置1,压缩一下
//compact方法会把limit位置重置为最大容量,即limit=capacity,这里就是10
buffer.position(1);
buffer.compact();
printBuffer(buffer);
System.out.println("---- compact() end ----");
//注意当前指针指向3,继续写入数据的话,就会覆盖后面的数据了。
System.out.print("End ByteBuffer, capacity is:"+buffer.capacity());
System.out.print(" ,limit is:"+buffer.limit());
System.out.println(" ,position is:"+buffer.position());
}
/**
* 输出buffer内容.
*/
public static void printBuffer(ByteBuffer buffer){
//记住当前位置
int p = buffer.position();
//指针挪到0
buffer.position(0);
//循环输出每个字节内容
for(int i=0;i