与NIO Channel进行交互时,将使用Java NIO Buffer。前面提到过,数据从Channel读到Buffer,或从Buffer写到Channel。
缓冲区本质上是一个内存块,可以在其中写入数据,然后在以后再次读取。该内存块在NIO中封装成Buffer对象,该对象提供了一组方法,可以更轻松地使用该内存块。
1.Buffer的基本使用
使用Buffer读取和写入数据通常遵循以下四个步骤:
(1)写数据到 Buffer
(2)调用buffer.flip()
(3)从Buffer读取数据
(4)调用buffer.clear() 或buffer.compact()
当你将数据写入缓冲区时,缓冲区会跟踪已写入多少数据。一旦需要读取数据,就需要调用flip()方法,将缓冲区从写入模式切换到读取模式。在读取模式下,缓冲区使你可以读取写入缓冲区的所有数据。
读取所有数据后,你需要清除缓冲区,以准备再次写入。你可以通过两种方式执行此操作:通过调用clear()或通过调用compact()。 clear()方法清除整个缓冲区。 compact()方法仅清除已经读取的数据。任何未读的数据都将移到缓冲区的顶部,并且现在写入到缓冲区的数据都将紧随其后。
一个简单的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();
2.Buffer Capacity,Position,Limit
一个Buffer需要熟悉三个属性,以便了解缓冲区的工作方式:
capacity
position
limit
position和limit的含义取决于缓冲区是处于读取模式还是写入模式。而capacity不管缓冲区模式如何,始终相同。
这是写和读模式下的capacity,position和limit的说明:
Capacity
作为一个内存块,缓冲区具有一定的固定大小,也称为“容量”,你只能向缓冲区写入不超过容量的字节。一旦缓冲区已满,你需要先清空它(读取数据或清除它),然后才能向其中写入更多数据。
Position
当你将数据写入缓冲区时,你需要在特定位置position进行。最初的位置为0。将字节写入缓冲区后,该位置将前进以指向缓冲区中的下一个单元,位置最大为capacity-1。 从缓冲区读取数据时,也要从给定位置读取数据。当你将缓冲区从写入模式翻转到读取模式时,该位置将重置为0。从缓冲区读取数据时,你将从该位置进行读取,并且位置前进到下一个要读取的位置。
Limit
在写模式下,缓冲区的limit是可以写入缓冲区的数据量。在写模式下,limit等于缓冲区的容量。 将缓冲区切换到读取模式时,limit表示可以从数据中读取多少数据。因此,将Buffer切换到读取模式时,将limit设置为写入模式的position。换句话说,你可以读取与写入的字节一样多的字节(limit设置为写入的字节数,该字节数由position标记)。
3.Buffer 类型
Java NIO中有以下几个类型的缓冲区:
ByteBuffer
MappedByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
这些缓冲区代表不同的数据类型。换句话说,可以使用char,short,int,long,float或double代替缓冲区中的字节。
MappedByteBuffer有点特殊,后面单独介绍。
分配缓冲区
要获取Buffer对象,必须先分配它。每个Buffer类都有一个执行此操作的allocate()方法。
这是显示容量为48个字节的ByteBuffer分配的示例:
ByteBuffer buf = ByteBuffer.allocate(48);
这是一个分配了1024个字符的CharBuffer示例:
CharBuffer buf = CharBuffer.allocate(1024);
写数据到Buffer
你可以通过两种方式将数据写入Buffer:
将数据从Channel写入Buffer ;
通过Buffer的put()方法将数据写入Buffer;
这是一个示例,显示Channel如何将数据写入Buffer:
int bytesRead = inChannel.read(buf); //read into buffer.
这是一个通过put()方法将数据写入Buffer的示例:
buf.put(127);
put()方法还有许多其他版本,允许你以多种不同方式将数据写入Buffer中。例如,在特定位置写入或将字节数组写入到缓冲区。有关更多详细信息,请参见JavaDoc。
flip()
flip()方法将Buffer从写入模式切换到读取模式。调用flip()会将position重置为0,并将limit设置为原来的position。换句话说,position现在标记了读取位置,而limit标记了有多少字节可写入缓冲区。
从Buffer读数据
你可以通过两种方式从缓冲区读取数据
从缓冲区读取数据到Channel。
使用get()方法从缓冲区读取数据。
这是如何从缓冲区将数据读取到Channel的示例:
//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保持不变,因此仍标记可以从缓冲区读取多少个字节。
clear() 和 compact()
一旦完成了从缓冲区中读取数据的操作,则必须使缓冲区准备好再次写入。可以通过调用clear()或通过调用compact()来实现。
如果调用clear(),则position将重新设置为0,limit重新设置为capacity。换句话说,缓冲区被清除,缓冲区中的数据未清除。只标记从哪里开始写入数据。
如果在调用clear()时缓冲区中有任何未读取的数据,则该数据将被“遗忘”,这意味着你将不再具有任何标记来读取那些尚未读取的数据。
如果缓冲区中仍然有未读取的数据,并且你想稍后再读取,但是现在需要先进行一些写入操作,请调用compact()而不是clear()。
compact()将所有未读数据复制到Buffer的头部。然后将位置设置在最后一个未读元素之后。就像clear()一样,limit属性仍然设置为capacity。现在,缓冲区已准备好写入,但是不会覆盖未读取的数据。
mark() 和 reset()
你可以通过调用Buffer.mark()方法在Buffer中标记位置。然后,可以稍后通过调用Buffer.reset()方法将位置重置回前面标记的位置。例:
buffer.mark();
//call buffer.get() a couple of times, e.g. during parsing.
buffer.reset(); //set position back to mark.
4.equals() 和 compareTo()
可以使用equals()和compareTo()比较两个缓冲区。
equals()
如果两个缓冲区相等,则:
它们具有相同的类型(字节,字符,整数等)
它们在缓冲区中具有相同数量的剩余字节,字符等。
所有剩余的字节,字符都是相等的。
equals仅比较Buffer的一部分,而不比较其中的每个元素。实际上,它只是比较缓冲区中的存在的元素。
compareTo()
compareTo()方法比较两个缓冲区的其余元素(字节,字符等),一般用于排序。在以下情况下,一个缓冲区被认为比另一个缓冲区“小”:
Buffer中的第一个元素比另一个Buffer中第一个元素小。
所有元素都相等,但是第一个缓冲区元素少于第二个缓冲区的元素。
总结:
我认为 NIO Buffer 中以上内容是最重要的!
原文地址:https://www.zhblog.net/go/java/tutorial/java-nio-buffer?t=609