Java NIO
缓冲区在与NIO
通道交互时使用。数据从通道读取到缓冲区,然后从缓冲区写入通道。
缓冲区本质上是一块内存,可以在其中写入数据,然后再进行读取。这个内存块被封装在一个NIO
Buffer
对象中,该对象提供了一组方法,可以更容易地使用内存块。
Buffer
基础用法:
- 写入数据到
Buffer
缓冲区中 - 调用
buffer.flip()
,转换Buffer
缓冲区读写模式 - 从
Buffer
缓冲区中读取数据 - 调用
buffer.clear()
和buffer.compact()
对缓冲区数据进行处理
// 使用缓存区进行内存分配: ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 从Channel中读取数据,写入到Buffer中: int read = fileChannel.read(byteBuffer); while (-1 != read) { // flip转换读写模式: byteBuffer.flip(); while (byteBuffer.hasRemaining()) { System.out.print((char) byteBuffer.get()); } // 清除缓冲区内容: byteBuffer.clear(); read = fileChannel.read(byteBuffer); }
将数据写入缓冲区时,缓冲区会跟踪写入的数据量。一旦需要读取数据,就需要使用flip()
方法调用将缓冲区从写入模式切换到读取模式。在读取模式下,缓冲区允许您读取写入缓冲区的所有数据。
一旦读取了所有数据,就需要清除缓冲区,以便再次写入。可以通过两种方式来实现:通过调用clear()
或通过调用compact()
。clear()
方法清除整个缓冲区。compact()
方法只清除您已经读取的数据。任何未读数据都会移动到缓冲区的开头,现在数据将在未读数据之后写入缓冲区。
Buffer
核心属性:
缓冲区有三个您需要熟悉的属性:capacity
缓冲区容量、position
指针索引位置、limit
缓冲区限制。
position
位置和limit
限制的含义取决于缓冲区是处于读取模式还是写入模式。无论缓冲模式如何,capacity
容量含义不变。
public abstract class Buffer { // ...... // Invariants: mark <= position <= limit <= capacity private int mark = -1; // 标记 private int position = 0; private int limit; private int capacity; }
Capacity
容量:
Buffer
作为一个内存块,缓冲区有一定的固定大小,也称为“容量”。只能将容量字节、长度、字符等写入缓冲区。一旦缓冲区已满,就需要清空它(读取数据或清除它),然后才能向其中写入更多数据。
Position
指针位置:
指针位置,下一个要被读或写的元素索引,每次读写缓冲区数据时都会改变值,为下次读写做准备。
将数据写入缓冲区时,会在某个位置进行写入。最初位置为0
。当一个字节、长等已写入缓冲区时,该位置将前进到指向缓冲区中的下一个单元格以插入数据。位置可以最大化为容量-1
。
当从缓冲区读取数据时,也可以从给定的位置读取数据。将“缓冲区”从写入模式翻转到读取模式时,位置会重置回0
。当从缓冲区读取数据时,从位置读取数据,位置将前进到下一个要读取的位置。
limit
缓冲区限制:
表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的。
在写入模式下,缓冲区的限制是可以写入缓冲区的数据量的限制。在写入模式下,限制等于缓冲区的容量。
将“缓冲区”切换到读取模式时,limit
表示可以从数据中读取多少数据的限制。所以,当将缓冲区翻转到读取模式时,将限制设置为写入模式的写入位置。换句话说,可以读取写入的字节数(限制设置为写入的字节数量,由位置标记)。
Buffer
常用类型:
Java NIO
具有以下缓冲区类型:
ByteBuffer
MappedByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
给Buffer
分配内存(创建Buffer
实例):
要获得Buffer
对象,您必须首先对其进行分配。每个Buffer
类都有一个allocate()
方法来完成此操作。
给ByteBuffer
分配1024
字节内存:
// 给Buffer分配内存: ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
给CharBuffer
分配1024
字符:
CharBuffer charBuffer = CharBuffer.allocate(1024);
向Buffer
中写入数据:
可以通过两种方式向缓冲区中写入数据:
- 可以从
Channel
通道中获取到数据,写入Buffer
缓冲区。
// 读取Channel中的数据到Buffer中: int bytesRead = inChannel.read(buf);
- 可以通过调用
put()
方法,直接写入数据到Buffer
缓冲区中。
buf.put(127);
flip()
方法 转换Buffer
读写模式:
flip()
方法可以转换Buffer
的读写模式,将Buffer
从写入模式切换到读取模式,调用flip()
可以将position
设置为0
,并将limit
设置为些时的position
位置。
换句话说,position
现在标记读取位置,limit
标记有多少字节、字符等被写入缓冲区(可以读取的字节、字符等等的限制)。
从Buffer
中读取数据:
可以通过两种方式从缓冲区中读取数据:
- 可以读取
Buffer
中的数据到Channel
中。
// 向通道中写入数据: int bytesWritten = inChannel.write(buf);
- 可以通过
get()
方法获取到Buffer
中的数据。
byte aByte = buf.get();
Buffer
还提供了额外方法用于操作position
、limit
等属性的方法:
reword()
:
Buffer.rewind()
将position
位置设置回0
,这样您就可以重新读取缓冲区中的所有数据。限制保持不变,因此仍然标记可以从缓冲区读取多少元素(字节、字符等)。
mark()
和reset()
:
mark()
是在读取时,做一个标记,即使 position
改变,只要调用 reset()
就能回到 mark
的位置。注意:rewind()
和 flip()
都会清除 mark
位置。
buffer.mark(); //call buffer.get() a couple of times, e.g. during parsing. buffer.reset(); //set position back to mark.
clear()
和compact()
:
一旦完成了从缓冲区中读取数据,就必须使缓冲区做好再次写入的准备。您可以通过调用clear()
或compact()
来实现这一点。
如果调用clear()
,则位置将设置回0
,并限制容量。换句话说,缓冲区被清除。缓冲区中的数据不会被清除。只有指示可以将数据写入缓冲区的位置的标记。
当您调用clear()
时,如果缓冲区中有任何未读数据,则该数据将被“遗忘”,这意味着您不再有任何标记来指示哪些数据已被读取,哪些数据未被读取。
如果缓冲区中仍有未读数据,并且您想稍后读取,但需要先进行一些写入,请调用compact()
而不是clear()
。
compact()
将所有未读数据复制到Buffer的开头。然后它将位置设置在最后一个未读元素的正后方。limit
属性仍然设置为capacity
,就像clear()
一样。现在缓冲区已准备好写入,但不会覆盖未读数据。
equals()
和compareTo()
:
equals()
两个缓冲区相等,如果:它们属于相同的类型(字节、字符、int
等)它们在缓冲区中具有相同数量的剩余字节、字符等。所有剩余的字节、字符等都是相等的。正如您所看到的,equals
只比较Buffer
的一部分,而不是其中的每一个元素。事实上,它只是比较Buffer
中的其余元素。
compareTo()
方法比较两个缓冲区的剩余元素(字节、字符等),用于例如排序例程。如果出现以下情况,则认为缓冲区比另一个缓冲区“小”:与另一个缓冲器中的对应元素相等的第一个元素小于另一个缓冲区中的元素。所有元素都是相等的,但第一个缓冲区在第二个缓冲区之前用完了元素(它的元素更少)。