原文地址: link.
4.Java NIO Buffer
在与NIO Channel交互时使用Java NIO Buffer。如你所知,数据从Channel读入Buffer,并从Buffer写入Channel。
Buffer本质上是一个可以写入数据的内存块,然后可以再次读取。此内存块包含在NIO Buffer对象中,该对象提供了一组方法,可以更轻松地使用内存块。
Basic Buffer Usage
使用Buffer读取和写入数据通常遵循以下四个步骤:
- 将数据写入Buffer
- 调用buffer.flip( )
- 从Buffer读取数据
- 调用buffer.clear()或buffer.compact()
将数据写入Buffer时,Buffer会跟踪你写入的数据量。一旦需要读取数据,就需要使用flip( )方法调用将Buffer从写入模式切换到读取模式。在读取模式下,Buffer允许你读取写入Buffer的所有数据。
一旦读完所有数据,就需要清除Buffer,以便再次写入。你可以通过两种方式完成此操作:通过调用clear( )或调用compact( )。clear( )方法清除整个Buffer。compact( ) 方法仅清除你已读取的数据。任何未读数据都会移动到Buffer的开头,现在Buffer写入数据将在未读数据之后。
这是一个简单的Buffer用法示例,其中写入,翻转,读取和清除操作以粗体显示:
public class Exam2 {
public static void main(String[] args) throws IOException {
RandomAccessFile aFile = new RandomAccessFile("data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//创建容量为48字节的Buffer
ByteBuffer buf = ByteBuffer.allocate(48);
//读入Buffer
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
buf.flip(); //使Buffer准备好读取
while (buf.hasRemaining()) {
//一次读取1个字节
System.out.print((char) buf.get());
}
//使Buffer准备好写入
buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();
}
}
Buffer Capacity(容量), Position(位置) 和 Limit(限制)
Buffer本质上是一个可以写入数据的内存块,然后可以再次读取。此内存块包含在NIO Buffer对象中,该对象提供了一组方法,可以更轻松地使用内存块。
Buffer有三个属性需要熟悉,以便了解其Buffer工作原理。这些是:
- capacity(容量)
- position(位置)
- limit(限制)
position和limit的含义取决于Buffer是处于读取还是写入模式。无论缓冲模式如何,capacity总是意味着相同。
以下是写入和读取模式下的capacity,position和limit的说明。解释如下图所示。
Capacity
作为一个内存块,Buffer具有一定的固定大小,也称为“容量”。可以将字节,长整数,字符等写入Buffer但不能超过Buffer的容量大小。Buffer已满后,你需要清空它(读取数据或清除它),然后才能将更多数据写入其中。
Position
当你将数据写入时Buffer,你可以在某个位置执行此操作。最初位置为0,当一个字节,长整数等已写入Buffer时,位置被提前指向Buffer中的下一个单元以插入数据。位置最大为:capacity - 1。
当从Buffer读取数据时,也可以从给定位置读取数据。将Buffer从写入模式切换到读取模式时,位置将重置为0。当从Buffer读取数据时,将从位置读取数据,并将位置提前到下一个要读取的位置。
Limit
在写入模式下,Buffer的限制(limit)是可以写入缓冲区的数据量的限制。在写入模式下,limit等于Buffer的capacity。
当Buffer翻转为读取模式时,限制(limit)意味着可以从数据中读取的数据量的限制。因此,当将Buffer翻转到读取模式时,限制(limit)被设置为写入模式当时的位置(position)。换句话说,你可以读取写入的字节数(限制(limit)设置为写入的字节数,由位置标记)。
Buffer Types
Java NIO附带以下Buffer类型:
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
如你所见,这些Buffer类型代表不同的数据类型。换句话说,它们允许你使用char,short,int,long,float或double来处理Buffer中的字节。
MappedByteBuffer有点特殊,将在其自己的文本中介绍。
Allocating a Buffer
要获取Buffer对象,必须先分配它。每个Buffer类都有一个allocate( )方法来执行此操作。下面是一个显示ByteBuffer分配的示例,容量为48字节:
ByteBuffer buf = ByteBuffer.allocate(48);
下面是一个为1024个字符分配空间的CharBuffer示例:
CharBuffer buf = CharBuffer.allocate(1024);
将数据写入Buffer
可以通过两种方式将数据写入Buffer:
- 将数据Channel写入Buffer
- 通过Buffer的put()方法,自己将数据写入缓冲区
下面是一个示例,显示了Channel如何将数据写入Buffer:
int bytesRead = inChannel.read(buf); //读入Buffer
这是一个通过put()方法将数据写入Buffer的示例:
buf.put(127);
put( )方法还有许多其他版本,允许你以多种不同方式将数据写入Buffer。例如,在特定位置写入,或将一个字节数组写入Buffer。有关更多详细信息,请参阅JavaDoc以获取具体的Buffer实现。
flip( )
flip( )方法将Buffer从写入模式切换到读取模式。调用flip( )会将position设置为0,并将limit设为position之前的位置。
换句话说,position现在标记读取位置,limit标记有多少字节,字符等写入Buffer ——可读取的字节数,字符数等。
从Buffer读取数据
有两种方法可以从Buffer读取数据。
- 将数据从Buffer读入Channel。
- 使用其中的get( )方法自己从Buffer读取数据。
以下是如何将Buffer中的数据读入Channel的示例:
// 从Buffer读入Channel
int bytesWritten = inChannel.write(buf);
以下是使用get( )方法从Buffer读取数据的示例:
byte aByte = buf.get();
get( )方法还有许多其他版本,允许你以多种不同方式从Buffer中读取数据。例如,读取特定位置,或从Buffer读取字节数组。有关更多详细信息,请参阅JavaDoc以获取具体的Buffer实现。
rewind( )
Buffer.rewind( )方法将position设置回0,这样你就可以重新读取Buffer中的所有数据了。limit保持不变,因此仍然标记可以从缓冲区读取多少元素(字节,字符等)。
clear( )和compact( )
完成从Buffer中读取数据后,必须使Buffer准备好再次写入。你可以通过调用clear( )或compact( )来实现。
如果在调用clear( )方法,position设置回0和limit设置为capacity。换句话说,Buffer被清除了。Buffer中的数据未清除。只有标记告诉你可以将数据写入Buffer的位置。
如果在调用clear( )方法时缓冲区中有未读数据,该数据将被“遗忘”,这意味着你不再有任何标记来告知已读取的数据以及未读取的数据。
如果Buffer中仍有未读数据,并且你想稍后读取它,但你需要先进行一些写入,请调用compact( )方法而不是clear( )方法。
compact( ) 方法将所有未读数据复制到Buffer的开头。然后它将position设置在最后一个未读元素之后。limit属性仍然设置为capacity,就像clear( )那样。现在Buffer已准备好写入,但是不会覆盖未读数据。
mark( )和reset( )
你可以通过调用Buffer.mark( )方法获取Buffer中position的位置。然后,你可以通过调用Buffer.reset( )方法将position重置回标记位置。这是一个例子:
buffer.mark();
// 多次调用buffer.get(),例如在解析期间。
buffer.reset(); //将位置设置回标记。
equals( )和compareTo( )
可以使用equals( )和compareTo( )比较两个Buffer。
equals( )
如果,两个Buffer相等:
- 它们的类型相同(byte,char,int等)
- 它们在Buffer中具有相同数量的剩余字节,字符等。
- 所有剩余的字节,字符等都相等。
如你所见,equals仅比较Buffer的一部分,而不是它内部的每个元素。实际上,它只是比较Buffer中的剩余元素。
compareTo( )
compareTo( )方法比较两个Buffer的剩余元素(字节,字符等),用于例如排序例程。在以下情况下,Buffer被视为“小于”另一个Buffer:
- 与另一个Buffer中的对应元素相等的第一个元素小于另一个Buffer中的元素。
- 所有元素都相等,但第一个Buffer在第二个Buffer之前耗尽了元素(它有更少的元素)。
5.Java NIO Scatter / Gather
Java NIO具有内置的scatter(分散)/gather(聚集)支持。Scatter / gather是用于读取和写入通道的概念。
从通道读取的scatter是将数据读入多个缓冲区的读取操作。因此,Channel将来自Channel的数据“分散”到多个Buffer中。
对Channel的收集写入是将来自多个Buffer的数据写入单个Channel的写入操作。因此,Channel将来自多个Buffer的数据“收集”到一个Channel中。
在需要单独处理传输数据的各个部分的情况下,scatter/gather非常有用。例如,如果消息由标题和正文组成,则可以将标题和正文保留在单独的缓冲区中。这样做可以使你更容易分别使用标题和正文。
Scattering Reads
“散射读取”将数据从单个通道读入多个Buffer。以下是该原则的说明:
以下是Scatter原理的说明:
下面是一个代码示例,演示如何执行scatter读取:
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);
注意Buffer如何首先插入到数组中,然后将数组作为参数传递给channel.read( )方法。然后read( )方法,按照Buffer在数组中出现的顺序从Channel写入数据。一旦Buffer已满,Channel就会继续填充下一个Buffer。
散射读取在进入下一个Buffer之前填充一个缓冲区,意味着它不适合动态大小的消息部分。换句话说,如果你有一个标题和一个正文,并且标题是固定大小(例如128个字节),那么散射读取工作正常。
Gathering Writes
“聚集写入”将来自多个缓冲区的数据写入单个通道。以下是该原理的说明:
这是一个代码示例,演示如何执行收集写入:
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);
Buffer数组传递给write( )方法,该方法按照在数组中遇到的顺序写入Buffer的内容。仅写入缓冲区的位置和限制之间的数据。因此,如果缓冲区的容量为128字节,但只包含58个字节,则只有58个字节从该缓冲区写入通道。因此,与散射读取相比,聚集写入与动态大小的消息部分可以正常工作。
【上篇】Java NIO学习教程(一)==>点击
【下篇】Java NIO学习教程(三)==>点击