.基本的Buffer用法
.Buffer容量、位置、及 限制
.Buffer的类型
.分配一个Buffer
.把数据写入到buffer中
.flip()
.从Buffer中读数据
.rewind()
.clear() 和compact()
.mark() 和reset()
.equals()和compareTo()
一、基本Buffer用法
java NIO Buffer经常用于和NIO Channel打交道。正如你所知的那样,数据从channel读取到buffer中以及从buffer中写入到channel中。
//即:Buffer是数据和Channel的桥梁
buffer在本质上就是一个内存块,你可以把数据写入到buffer中。之后,你还能从buffer中再次读取数据。这个内存块被封装成一个NIO Buffer对象。这个Buffer对象提供了一组操作内存块的便捷方法。
在使用Buffer读写数据时,通常遵循下面四个步骤:
1.把数据写入到buffer中
2.调用buffer.flip()方法
3.从buffer中把数据读出来
4.调用buffer.clear()或buffer.compact()
当你写入数据到buffer中时,buffer会记录下你已经写了多少数据。一旦你想读取数据时,你就可以调用flip()方法把buffer从写模式(writing mode)切换到读模式(reading mode)。在读模式下,buffer允许你读取所有被写入到buffer中的数据。
一旦你读取了所有的数据后,你就需要清空一下这个buffer,这样它就能再次把数据写入到buffer中。你有两种方式做到这一点:1.是调用clear()方法,2是调用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 Capacity, Position 以及 limit
Buffer本质上是一个可以写入数据的内存块,之后,你又能再次从中读取数据。这个内存块被封装成一个NIO Buffer对象。它提供了一系列操作内存块的方法。
一个Buffer有三个属性,通过它们你可以理解一个Buffer是如何工作的:
capacity
position
limit
position和limit的意义取决于Buffer是处于read模式还是处于write模式。而无论在那种模式下,capacity都是相同的。
这里有一个关于 capacity、position、limit在写和读模式下的的图例。详细地解释会在图例之后的下一个章节讲述。
Capacity(容量)
作为一个内存块,Buffer都有一个确定大小,也被称为它的容量。你只能写capacity大小个字节的数据,long类型,char类型等等。一旦Buffer满了之后,在你准备向该buffer中写入更多数据之前,你需要先清空它(读取其中的数据或清除它)。
Postition
当你把数据写入到Buffer中时,你会在一个确定的位置操作。最初始的位置是0。当一个byte,long类型等的数据已经被写入到Buffer中时,位置position就会前进到buffer中的下一个位置处。Position的最大值是capacity-1。
当你从一个Buffer中读取数据时,你也是从一个给定位置处开始的。 当你flip一个Buffer时,会使其从写模式(writing mode)切换到读模式(reading mode),同时position被重置回0。当你从Buffer中读数据时,你会从position位置处开始读,并且position还会前进到下一个要读取的位置。 //这里的position类似于指针。
Limit
在写模式下,一个Buffer的limit就是你总共能向这个buffer中写入多少数据。在写模式下,limit就等价于Buffer的容量。
当把一个Buffer切换到读模式下时,limit就意味着你能从该Buffer中读取多少数据。因此,当flip一个buffer到读read模式下时,limit就被设置为写模式writeMode时的写位置write position。换句话说,你写入多少字节的数据,你就能读取多少字节的数据(limit被设置为 写入数据的字节数,被position所标识)。
三、Buffer类型
Java NIO提供了下面几种Buffer类型:
.ByteBuffer
.MappedByteBuffer
.CharBuffer
.DoubleBuffer
.FloatBuffer
.IntBuffer
.LongBuffer
.ShortBuffer
正如你看到的,这些Buffer类型分别代表了不同的数据类型。换句话说,它们允许你在Buffer中操作char,short,int,long,float或double类型的数据。
MappedByteBuffer稍微有点不同,我们将在一个独立的章节中讲述它。
四、分配一个Buffer
要获取一个Buffer对象,你首先需要allocate一个。每一个Buffer类都有一个allocate()方法。
下面的这个例子展示了 如何allocate一个48字节的ByteBuffer:
ByteBuffer buf=ByteBuffer.allocate(48);
下面的例子,则是分配了一个1024个字符大小的CharBuffer类型的buffer:
CharBuffer buf = CharBuffer.allocate(1024);
五、向Buffer中写入数据
有俩种方式可以向Buffer中写入数据:
1.从Channel中把数据写入到一个Buffer中。
2.向Buffer自身中写入数据,通过buffer的put()方法。
下面的这个例子演示了一个Channel如何把数据写入到一个Buffer中:
int bytesRead = inChannel.read(buf); //read into buffer.
下面的例子则是通过put()方法向Buffer中写入数据:
buf.put(127);
还有许多其他的put()方法,可以让你以不同的方式向Buffer中写入数据。例如,在特定位置写入,或这把一个字节数组写入到buffer中。要想获取更多细节,请查看具体buffer实现的JavaDoc文档。
flip()
flip()方法会把一个Buffer从写模式切换到读模式。调用flip()方法会把position的值设置回0同时把limit的值设置成刚刚position所处的位置。换句话说,现在position标记读位置,limit标记了有多少字节,字符等被写进到该buffer中。-可以读取的字节,字符等的上限。
六、从Buffer中读取数据
有俩种方式从Buffer中读取数据:
1.从buffer中读取数据到一个channel中。
2.从buffer本身中读取数据,使用get()方法。
下面有个例子展示了如何从buffer中读取数据到一个channel中:
//read from buffer into channel.
int bytesWritten = inChannel.write(buf);
下面的这个例子,展示了使用get()方法从buffer中读取数据:
byte aByte = buf.get();
还有很多重载的get()方法,它们允许你以许多不同的方式从Buffer中读取数据。例如,从指定位置处读,或 从buffer中读取一字节数组的数据。
rewind()
Buffer.rewind()方法会把position设置回0,因此你可以重新读取buffer中的所有数据。limit的值会保持原样,因此,limit的值仍然标识着可以从Buffer中读取多少元素(字节,字符,等等)。
clear()和compact()
一旦你完成从Buffer读取数据之后,你就不得不让该buffer为再次的写入做好准备。通过调用clear()或compact()方法做到这一点。如果你调用了clear()的话,position会被设置为0,limit会被设置为容量大小。换句话说,该Buffer被清除了,Buffer中的数据并没有被清除。这些标记只是告诉你该把数据写到buffer的什么地方。
当你调用clear()方法时,如果buffer中还有其他未读取的数据,那么那些数据就会被"遗忘",也就是意味着:不再有任何标记可以告诉你那些数据已经被读取了,那些数据还没有别读取。
如果Buffer中尚有未读取的数据,并且你希望在以后再读取它,但是你又需要先做一些其他的事情,这时,你就应该调用compact()而不clear()。
compact()方法会把所有尚未读取的数据复制到Buffer的起始处。之后,把position的值设为最后一个未读取数据元素的后面。limit此时仍然被设置为capacity的大小。就像clear()方法一样。现在,该Buffer就准备好写入操作了。但是这样做的话,你不会在写入时覆盖未读取的数据。
mark()和reset()
通过调用Buffer.mark()方法,你可以标记一个Buffer中的给定位置。这样,在之后,你就可以调用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()
可以使用equals()和compareTo()方法来比较两个buffer。
equals():
两个buffer只有在下面的条件满足时才相等:
1.它们是同一类型的Buffer(例如,字节,char,int等等)
2.它们在buffer中剩余的字节数量(或字符数量)相等。
3.所有剩余的字节,字符等都相等。
我们可以看到,equals()比较的仅仅是Buffer中的一部分,而不是它内部的每一个单一元素。事实上,它仅仅比较Buffer中剩余的元素。
compareTo()
compareTo()方法会比较两个buffer中的剩余元素(如,字节,字符等), 在下面这些情况下,一个buffer会被视为比另一个“小”:
1.buffer中的第一个元素(与另一个buffer中的相应元素相等的那个) 比另一个buffer中的要小。
2.所有的元素都相等,但是第一个buffer在第二个buffer进行之前已经用完了元素(它里面的元素很少)