Java NIO Buffers用来和NIO Channels交互。正如前文所述,数据从通道中读到缓冲区,或者从缓冲区写到通道。 缓冲区本质上是一块能写入数据,并延迟读取的内存。这块内存被包装成一个NIO Buffer类,并提供了一组方法简化对它的访问。
对Buffer的读写一般遵循以下四个步骤:
向缓冲区写入数据时,缓冲区记录写入了多少数据。一旦要读取数据,需要调用flip()方法将缓冲区从写模式转换到读模式。在读模式下,可以读取到之前写入缓冲区的数据。
完成数据读取后,需要清空缓冲区,让它可以再次被写入。有两种方式能够清空缓冲区:调用clear()方法或者compact()方法。clear()方法清空整个缓冲区。compact()方法仅清空已经读取的部分。所有未读取的数据被移动到缓冲区的开头。新数据将从未读取数据的后面开始写入。
以下是一个使用Buffer的简单实例,注意其中的write,flip,read,clear操作(原文中意为这四个操作用黑体标识,但是简书不支持)。
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf); //read into buffer.
while (bytesRead != -1) {
buf.flip(); //make buffer ready for read
while(buf.hasRemaining()){
System.out.print((char) buf.get()); // read 1 byte at a time
}
buf.clear(); //make buffer ready for writing
bytesRead = inChannel.read(buf);
}
aFile.close();
缓冲区本质上是一块可以写入数据,然后从中读取的内存。这块内存被包装成一个NIO Buffer对象,提供了一组方法用来简化对内存的访问。 为了了解Buffer的工作原理,需要熟悉它的三个属性:
Buffer capacity,postion and limit in write and read mode
作为一个内存块,Buffer有固定的大小,叫做"capacity(容量)"。只能往里面写入capacity个bytes,long,chars等类型。一旦Buffer满了,需要清空它才能继续往里面写入数据。
数据写入Buffer中时,需要知道一个确切的位置。初始位置是0,当一个byte,long等数据写入后,position移动到buffer中的下一个插入数据的位置。position最大可为capacity-1. 从buffer读取数据时,也需要从某个特定的位置读。当buffer被从写模式flip成读模式时,position重置为0.当从Buffer中读数据时,就从position开始,然后position往后移动到下一个读的位置。
在写模式中,Buffer的Limit是对能写入多少数据的限制。写模式中limit等于Buffer的容量(capacity) 当把Buffer 转换到读模式后,limit代表能从buffer中读取多少数据。所以,buffer转换到读模式时,limit被设定为写模式中写入的位置。也就是说,能从buffer中读取的字节数就等于它被写入的字节数(limit被设定为写入的字节数,在写模式中就是position。)
Java NIO 中有以下Buffer类型:
为了获得一个Buffer对象,需要先对它进行分配(allocate),每一个Buffer对象都有allocate()方法来完成这个工作。 例,分配了容量为48字节的ByteBuffer:
ByteBuffer buf = ByteBuffer.allocate(48);
例,分配容量为1024个字符的CharBuffer:
CharBuffer buf = CharBuffer.allocate(1024);
向Buffer中写入数据有两种方法:
int byteRead = inChannel.read(buf);// read into a buffer
例,通过put方法写入:
buf.put(127)
put方法有很多不同的版本,提供了数据写入buffer的不同方式。例如,在指定的位置写入,或者写入字节数组。更多关于缓冲区的细节可以参考JavaDoc。
flip()方法将Buffer从写模式转换到读模式。调用flip()会将position重置为0,将limit设置为刚才的position。 换句话说,position现在标记了读的位置,limit标记了有多少字节,字符等被写入到了buffer——也就是有多少字节,字符可以被读取。
有两种方法从Buffer中读取数据:
// read from buffer into channel
int bytesWritten = inChannel.write(buf);
例,用get()方法从Buffer中读:
byte aByte = buf.get();
get()方法有很多版本,提供了从Buffer读取数据的不同方式。例如,从指定的位置读取,或者读取字节数据。更多关于缓冲区的细节可以参考JavaDoc。
Buffer.rewind()方法将position重置为0。这样能够重读buffer中数据。limit保持不变,仍然表示能够从buffer中读取的数据量。
一旦读取完Buffer中的数据,需要让Buffer做好再次写入的准备。 可以通过调用clear()或者compact()方法实现。 clear()方法会将position重置为0,limit重置为capacity。也就是说,Buffer被清空了。Buffer中的数据并未清除,只有这些标记代表Buffer能被写入数据的位置。 如果Buffer中仍有未被读取的数据,clear()方法将导致它们被遗忘,这意味着不再有任何标记表明那些数据被读取了,哪些没有。 如果Buffer中仍然有未被读取的数据,且后续还需要读取它们,但此时需要先写入数据。就需要使用compact()方法代替clear()方法。 compact()将未读取的数据复制到Buffer 的开头,然后将position设置为最后一个未读元素的后面,limit属性和clear()方法一样,被设置为capacity。之后就可以往Buffer中写入数据了,并且不会覆盖未读数据。
可以通过调用Buffer.mark()方法在Buffer中标记一个指定的位置。之后可以通过Buffer.reset()方法恢复到这个位置。 例如:
buffer.mark();
// call buffer.get() a couple of time, e.g. during parsing.
buffer.reset;// set position back to mark
使用equals()和compareTo()方法可以比较两个缓冲区。
满足一下条件,判断两个缓冲区相等:
compareTo()方法比较了两个缓冲区中的剩余数据,在例如排序等需求中。如果满足一下条件,一个Buffer被认为小于另一个: