Java NIO Buffers用来和NIO Channels交互。正如前文所述,数据从通道中读到缓冲区,或者从缓冲区写到通道。
缓冲区本质上是一块能写入数据,并延迟读取的内存。这块内存被包装成一个NIO Buffer类,并提供了一组方法简化对它的访问。
Buffer的基本用法
对Buffer的读写一般遵循以下四个步骤:
- 写入数据到Buffer
- 调用buffer.flip()方法
- 从Buffer中读取数据
- 滴啊用buffer.clear()或者buffer.compact()方法
向缓冲区写入数据时,缓冲区记录写入了多少数据。一旦要读取数据,需要调用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();
Buffer的Capacity,Position,和Limit
缓冲区本质上是一块可以写入数据,然后从中读取的内存。这块内存被包装成一个NIO Buffer对象,提供了一组方法用来简化对内存的访问。
为了了解Buffer的工作原理,需要熟悉它的三个属性:
- capacity
- position
-
limit
position和limit的含义取决于Buffer处在读膜还是还是写模式。capacity的含义是不变的。
以下是一个关于capacity,position,limit在读写模式中的插图,详细的解释在插图后面:
Capacity
作为一个内存块,Buffer有固定的大小,叫做"capacity(容量)"。只能往里面写入capacity个bytes,long,chars等类型。一旦Buffer满了,需要清空它才能继续往里面写入数据。
Position
数据写入Buffer中时,需要知道一个确切的位置。初始位置是0,当一个byte,long等数据写入后,position移动到buffer中的下一个插入数据的位置。position最大可为capacity-1.
从buffer读取数据时,也需要从某个特定的位置读。当buffer被从写模式flip成读模式时,position重置为0.当从Buffer中读数据时,就从position开始,然后position往后移动到下一个读的位置。
Limit
在写模式中,Buffer的Limit是对能写入多少数据的限制。写模式中limit等于Buffer的容量(capacity)
当把Buffer 转换到读模式后,limit代表能从buffer中读取多少数据。所以,buffer转换到读模式时,limit被设定为写模式中写入的位置。也就是说,能从buffer中读取的字节数就等于它被写入的字节数(limit被设定为写入的字节数,在写模式中就是position。)
Buffer Types
Java NIO 中有以下Buffer类型:
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
可以看到,这些Buffer类型代表了不同的数据类型。换句话说,可以通过char,short,int,long,float或者double来操作buffer中的字节。
Buffer的分配
为了获得一个Buffer对象,需要先对它进行分配(allocate),每一个Buffer对象都有allocate()方法来完成这个工作。
例,分配了容量为48字节的ByteBuffer:
ByteBuffer buf = ByteBuffer.allocate(48);
例,分配容量为1024个字符的CharBuffer:
CharBuffer buf = CharBuffer.allocate(1024);
向Buffer中写
向Buffer中写入数据有两种方法:
- 从Channel中写到Buffer
- 通过缓冲区的put()方法写入数据。
例,从Channel写到Buffer:
int byteRead = inChannel.read(buf);// read into a buffer
例,通过put方法写入:
buf.put(127)
put方法有很多不同的版本,提供了数据写入buffer的不同方式。例如,在指定的位置写入,或者写入字节数组。更多关于缓冲区的细节可以参考JavaDoc。
flip()
flip()方法将Buffer从写模式转换到读模式。调用flip()会将position重置为0,将limit设置为刚才的position。
换句话说,position现在标记了读的位置,limit标记了有多少字节,字符等被写入到了buffer——也就是有多少字节,字符可以被读取。
从Buffer中读
有两种方法从Buffer中读取数据:
- 从缓冲区读取数据到通道。
- 用get()方法从缓冲区中读取数据
例,从缓冲区中读到通道
// read from buffer into channel
int bytesWritten = inChannel.write(buf);
例,用get()方法从Buffer中读:
byte aByte = buf.get();
get()方法有很多版本,提供了从Buffer读取数据的不同方式。例如,从指定的位置读取,或者读取字节数据。更多关于缓冲区的细节可以参考JavaDoc。
rewind()
Buffer.rewind()方法将position重置为0。这样能够重读buffer中数据。limit保持不变,仍然表示能够从buffer中读取的数据量。
clear()和compact()
一旦读取完Buffer中的数据,需要让Buffer做好再次写入的准备。
可以通过调用clear()或者compact()方法实现。
clear()方法会将position重置为0,limit重置为capacity。也就是说,Buffer被清空了。Buffer中的数据并未清除,只有这些标记代表Buffer能被写入数据的位置。
如果Buffer中仍有未被读取的数据,clear()方法将导致它们被遗忘,这意味着不再有任何标记表明那些数据被读取了,哪些没有。
如果Buffer中仍然有未被读取的数据,且后续还需要读取它们,但此时需要先写入数据。就需要使用compact()方法代替clear()方法。
compact()将未读取的数据复制到Buffer 的开头,然后将position设置为最后一个未读元素的后面,limit属性和clear()方法一样,被设置为capacity。之后就可以往Buffer中写入数据了,并且不会覆盖未读数据。
mark()和reset()
可以通过调用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()
使用equals()和compareTo()方法可以比较两个缓冲区。
equals()
满足一下条件,判断两个缓冲区相等:
- 类型相同(byte,char,int等)
- 剩余字节,字符等数量相同,
- 所有剩余的字节,字符相同
尅建,equals仅仅比较了Buffer中的部分数据,而不是其中的每一个元素,实际上,仅仅比较了Buffer中的剩余数据。
compareTo()
compareTo()方法比较了两个缓冲区中的剩余数据,在例如排序等需求中。如果满足一下条件,一个Buffer被认为小于另一个:
- 第一个不相等的元素小于另一个buffer中的对应元素(原文有误)。
- 所以的元素相等,但是第一个Buffer比另一个先耗尽(第一个Buffer的元素更少)。