Java NIO buffer 用于同NIO Channel交流。如同你知道的,数据从channel读取到buffer,从buffer写到channel。
一个buffer 本质上是一个你可以写数据的内存块,稍后你可以再次读。这个内存块包装到NIO buffer对象里,它提供了一个方法集合让使用内存块更加简单。
基础Buffer 使用
使用buffer去读取和写数据通常跟随下面4小步:
1、写数据到Buffer
2、调用buffer.flip()
3、从buffer读出数据
4、调用buffer.clear()或者buffer.compact()
当你写数据到buffer,buffer跟踪写了多少数据。一旦你需要读取数据,你需要使用flip()方法调用从写模式切换到读模式。在读模式里buffer让你读取所有写入到buffer里的数据。
一旦你读取完所有的数据,你需要清空buffer,为了再次写做准备。你有两种方法可以完成这个动作:通过调用clear()或者调用compact()。clear()方法清除整个buffer。compact()方法只清除已经读取的那部分数据。未读取的数据移动到buffer的起始处,并且数据将会被写到未读取的数据后面。
这里是简单的buffer使用例子,写,清除,读和清除操作:
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 容量,定位和限制
buffer本质上是一个你可以写数据的内存块,你可以稍后再次读。这个内存块包装在NIO buffer对象里,它提供了一系列简单的方法使用内存块。
一个buffer有三个你需要熟悉的属性,为了弄清楚buffer是怎么工作的,这三个是:
· capacity(容量)
· position (定位)
· limit (容量或者读取的大小)
position 和limit 依赖于buffer是在读模式里或者在写模式里。Capacity一般都是一样,不管buffer模式。
这是在读模式和写模式里容量,定位和限制的图表。具体的解释在图表后面的章节里。
在写模式和读模式里buffer的容量,定位和大小。
Capacity
开始一个内存块,buffer有一个确定的固定的大小,被叫做它的"capacity"。你只能写capacity bytes,longs,chars 等等到buffer里。一旦buffer满了,在你写更多的数据到它里面之前你需要清除它(读数据或者清除它)。
Position
当你写数据到buffer,做这些事需要在一个确切的position。初始化position是0。当byte,long等等已经写到buffer里,positio会指向buffer里的下一个单元为了将数据插入。Position最多可以变成capacity - 1。
当你从buffer里读取数据的时候,对于给定的postion要做的事情差不多。当你flip buffer从写模式到读模式,postion重置为0。当你从buffer里读取数据时,你从position开始读取,并且postion指向下一个position去读取。
Limit
在写模式里,buffer的limit的意思是你可以写多少数据到buffer里。在写模式里limit等同于buffer的容量。
当切换buffer到读模式,limit意味着你可以从数据里读取数据的限制。因此,当切换buffer到读模式,limit设置成写模式里的写的位置,你可以读取已经写入的数据(limit设置成已经写入的数量,这个数量被position标记)。
Buffer 类型
Java NIO带来了接下来的这些buffer类型:
· ByteBuffer(字节)
· MappedByteBuffer
· CharBuffer
· DoubleBuffer
· FloatBuffer
· IntBuffer
· LongBuffer
· ShortBuffer
如同你看到的,这些buffer类型表现了不同的数据类型。换句话说,它们让你使用buufer as char,short,long,float,或者double代替了字节。
MappedByteBuffer有点特殊,将在后面他有关的内容里进一步说明。
Allocating a Buffer
为了获取一个buffer对象你首先必须分配它。每个Buffer类都有一个allocate()方法来做这个事。这里是一个例子展示了ByteBuffer的分配,容量是48字节:
ByteBuffer buf = ByteBuffer.allocate(48);
下面是一个分配了1024字符空间的CharBuffer例子
CharBuffer buf = CharBuffer.allocate(1024);
Write Data to a Buffer
你可以写数据到buffer里通过两种方式:
1、从channel写到buffer
2、自己写数据到buffer,使用buffer的put()方法
这里是一个例子展示了channel怎么写数据到buffer:
int bytesRead = inChannel.read(buf); //read into buffer.
这里是一个例子展示了怎么使用put()方法写数据到Buffer:
bug.put(127);
有很多put()方法的变种,允许你在很多不同的方法里写数据到Buffer。例如,在指定的位置写,或者写一个bytes数组到buffer。查看JavaDoc以获得具体buffer实现的更多详情。
flip()
flip()方法切换buffer从写模式到读模式。调用flip()设置position为0,并且设置limit为刚才那个position的位置。
换句话说,position现在标记了读的位置,并且limit标志了多少bytes,chars等等被写入到buffer- 多少bytes,chars等等可以被读取。
Reading Data from a Buffer
这里有两种可以从buffer里读取数据的方法:
1、从buffer里读到channel。
2、自己从buffer里读取数据,使用get()方法。
这里是一个例子展示了怎么从buffer读取数据到channel:
//read from buffer into channel.
int bytesWritten = inChannel.write(buf);
这里是一个例子怎么使用eget()方法从Buffer里读取数据。
byte aByte = buf.get();
这里有很多get()方法的变种,允许你在不同的场景里从Buffer里读取数据。比如,在指定位置读取,或者从buffer里读取byte数组。查看JavaDoc以获得具体buffer实现的更多详情。
rewind()
Buffer.rewind()将position写回0,所以你可以在buffer里重新读取所有的数据。limit保持原样,那样仍然表示可以从buffer里读取多少元素(bytes,chars 等等)。
clear() 和 compact()
一旦你完成了从buffer里向外读取数据,你需要让buffer为再次写作准备。你可以通过调用clear()或者调用compact()来完成这个动作。
如果你调用clear(),position属性被设置回0并且limit设置成capacity。换句话说,buffer是cleared。在Buffer里的数据未清除。只有在标记告诉哪里你可以写数据到Buffer里。
如果在你调用clear()的时候还有一些未读取的数据,那么这些数据将会被遗忘,意味着不再有任何标记告诉哪些数据已经读取,哪些数据还没有读取。
如果在buffer里有还未读取的数据,并且你希望以后再读取,但是你又需要先写,使用compact()替代clear()操作。
compact()复制所有未读取的数据到Buffer的起始位置。然后设置position到上次未读取元素的右边。limit属性依然设置成capacity,这点跟clear()做的事情一样。现在buffer已经为写数据做好了准备,但是你不能覆盖那些未读取的数据。
mark() 和reset()
你可以通过调用Buffer.mark()方法来在Buffer里标识给定的positin。你可以稍后重置position回Buffer.reset()方法标记的position。这里是一个例子:
buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing.
buffer.reset(); //set position back to mark.
equals() 和compareTo()
可能使用equal()和compareTo()方法比较两个buffers。
equal()
如果出现下面的情况,两个Buffer相等:
1、它们的类型一样(bytes,char,int 等等)
2、它们在Buffer里剩余的bytes,chars等等的大小一样。
3、所有剩余的bytes,chars 等等相同。
如同你看到的那样,只比较Buffer的部分,而不是比较里面的每一个单个元素。在实际上,它只比较在Buffer里剩余的元素。
compareTo()
compareTo()方法比较在两个Buffer里的剩余的元素(bytes,chars等等),例如用于线程排序。buffer被考虑小于另一个buffer如果:
1、第一个元素等于另一个Buffer里相应的元素,小于在另外buffer里的第一个元素。
2、所有的元素都相等,但是首个buffer在第二个buffer前先用完。