Java NIO缓冲区

时间:2020-01-09 10:36:14  来源:igfitidea点击:

与NIO通道进行交互时,将使用Java NIO缓冲区。如我们所知,数据从通道读取到缓冲区,然后从缓冲区写入通道。

缓冲区本质上是一个内存块,我们可以其中写入数据,然后可以在以后再次读取。该内存块包装在NIO Buffer对象中,该对象提供了一组方法,可以更轻松地使用该内存块。

基本缓冲区使用量

使用"缓冲区"读取和写入数据通常遵循以下四个步骤:

  • 将数据写入缓冲区
  • 调用buffer.flip()
  • 从缓冲区读取数据
  • 调用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();

缓冲区容量,位置和限制

缓冲区本质上是一个内存块,我们可以其中写入数据,然后可以在以后再次读取。该内存块包装在NIO Buffer对象中,该对象提供了一组方法,可以更轻松地使用该内存块。

为了理解"缓冲区"的工作原理,"缓冲区"具有三个我们需要熟悉的属性。这些是:

  • 容量
  • 位置
  • 限制

" position"和" limit"的含义取决于" Buffer"是处于读取还是写入模式。不管缓冲模式如何,容量始终意味着相同。

这是写和读模式下的容量,位置和限制的说明。在图示之后的各节中进行解释。

<img src =“ http://theitroad.local/images/java-nio/buffers-modes.png” alt =“ Java NIO:写入和读取模式下的缓冲区容量,位置和限制。” />
在写和读模式下的缓冲区容量,位置和限制。</ b>

容量

作为一个内存块,"缓冲区"具有一定的固定大小,也称为"容量"。我们只能将"容量"字节,长整数,字符等写入缓冲区。缓冲区装满后,需要先清空(读取数据或者清除数据),然后才能向其中写入更多数据。

位置

当我们将数据写入"缓冲区"时,我们将在特定位置进行写入。最初的位置为0。当将字节,长等写入"缓冲区"时,该位置前进以指向缓冲区中的下一个单元以将数据插入其中。头寸最多可以变成"产能1"。

当我们从"缓冲区"读取数据时,我们也从指定位置读取数据。当我们将"缓冲区"从写入模式翻转到读取模式时,位置会重置为0。当我们从"缓冲区"中读取数据时,我们会从"位置"中进行读取,并且"位置"会前进到下一个位置进行读取。

限制

在写模式下,"缓冲区"的限制是可以写入缓冲区的数据量的限制。在写模式下,限制等于"缓冲区"的容量。

将"缓冲区"切换到读取模式时,限制表示可以从数据中读取多少数据的限制。因此,当将"缓冲器"翻转到读取模式时,将限制设置为写入模式的写入位置。换句话说,我们可以读取与写入的字节一样多的字节(限制设置为写入的字节数,该字节数由位置标记)。

缓冲区类型

Java NIO带有以下缓冲区类型:

  • 字节缓冲区
  • MappedByteBuffer
  • 字符缓冲区
  • 双缓冲
  • FloatBuffer
  • IntBuffer
  • 长缓冲
  • 短缓冲区

如我们所见,这些"缓冲区"类型代表不同的数据类型。换句话说,它们使我们可以将缓冲区中的字节作为char,short,int,long,float或者double来使用。

" MappedByteBuffer"有点特殊,将在其自己的文本中进行介绍。

分配缓冲区

要获得一个"缓冲区"对象,我们必须首先分配它。每个" Buffer"类都有一个" allocate()"方法来执行此操作。这是一个示例,显示了一个容量为48字节的`ByteBuffer'的分配:

ByteBuffer buf = ByteBuffer.allocate(48);

这是一个为1024个字符分配`CharBuffer'的示例:

CharBuffer buf = CharBuffer.allocate(1024);

将数据写入缓冲区

我们可以通过两种方式将数据写入"缓冲区":

  • 将数据从"通道"写入"缓冲区"
  • 我们可以通过缓冲区的put()方法将数据自己写入Buffer中。

这是一个示例,显示"通道"如何将数据写入"缓冲区":

int bytesRead = inChannel.read(buf); //read into buffer.

这是一个通过put()方法将数据写到Buffer中的例子:

buf.put(127);

put()方法还有许多其他版本,可让我们以多种不同方式将数据写入Buffer中。例如,在特定位置写入或者将字节数组写入缓冲区。有关更多详细信息,请参见JavaDoc以获取具体的缓冲区实现。

翻动()

flip()方法将Buffer从写模式切换到读模式。调用flip()position设置回0,并将limit设置到刚才的位置。

换句话说," position"现在标记了读取位置," limit"标记了将多少字节,字符等写入缓冲区,即可以读取多少字节,字符等的限制。

从缓冲区读取数据

我们可以通过两种方式从"缓冲区"中读取数据。

  • 从缓冲区读取数据到通道。
  • 我们可以使用get()方法之一从缓冲区中读取数据。

这是如何从缓冲区将数据读取到通道的示例:

//read from buffer into channel.
int bytesWritten = inChannel.write(buf);

这是一个使用get()方法从"缓冲区"中读取数据的示例:

byte aByte = buf.get();

get()方法还有许多其他版本,可让我们以多种不同方式从Buffer中读取数据。例如,在特定位置读取或者从缓冲区读取字节数组。有关更多详细信息,请参见JavaDoc以获取具体的缓冲区实现。

倒带()

Buffer.rewind()将position设置回0,因此我们可以重新读取缓冲区中的所有数据。限制没有改变,因此仍然标记了可以从"缓冲区"中读取的元素(字节,字符等)。

clear()和compact()

一旦完成从"缓冲区"中读取数据,就必须使"缓冲区"准备好再次写入。我们可以通过调用clear()或者compact()来实现。

如果调用clear(),则将position设置回0,将limit设置为capacity。换句话说,将清除"缓冲区"。不会清除"缓冲区"中的数据。只有标记告诉我们可以其中将数据写入"缓冲区"。

如果在调用" clear()"时"缓冲区"中有未读数据,则该数据将被"遗忘",这意味着我们将不再具有任何标记来告知已读取哪些数据和尚未读取哪些数据。

如果"缓冲区"中仍然有未读数据,并且我们想稍后再读取,但是我们需要先进行一些写操作,请调用" compact()"而不是" clear()"。

" compact()"将所有未读数据复制到" Buffer"的开头。然后将" position"设置在最后一个未读元素之后。就像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.

equals()和compareTo()

可以使用equals()和compareTo()比较两个缓冲区。

equals()

如果满足以下两个条件,则两个缓冲区相等:

  • 它们具有相同的类型(字节,字符,整数等)
  • 它们在缓冲区中具有相同数量的剩余字节,字符等。
  • 所有剩余的字节,字符等都是相等的。

如我们所见,equals仅比较"缓冲区"的一部分,而不是其中的每个元素。实际上,它只是比较"缓冲区"中的其余元素。

和compareTo()

compareTo()方法比较两个缓冲区的其余元素(字节,字符等),以用于例如排序例程。在以下情况下,一个缓冲区被认为比另一个缓冲区"小":

  • 与另一个缓冲区中的对应元素相等的第一个元素小于另一个缓冲区中的元素。
  • 所有元素都是相等的,但是第一个缓冲区要比第二个缓冲区用尽(它的元素更少)之前的元素用完。