Java NIO -Buffer
内容
Buffer的定义
Java NIO中的Buffer用于和NIO通道进行交互。如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
Buffer的三个属性:capacity,position,limit
缓冲区是特定原始类型的元素的线性有限序列。除了其内容之外,缓冲区的基本属性还包括其capacity,limit和position:
缓冲区的capacity是它包含的元素数量。缓冲区的capacity永远不会为负,也不会改变。
capacity的理解:
一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。
缓冲区的limit是不应读取或写入的第一个元素的索引。缓冲区的limit永远不会为负,也永远不会大于缓冲区的capacity。
limit的理解:
在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。
当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)
缓冲区的position是下一个要读取或写入的元素的索引。缓冲区的position永远不会为负,也不会大于其limit。postion的初始元素为0,最大值为capacity-1。
position的理解:
当读取数据时,也是从某个特定position读。当将Buffer从写模式切换到读模式,position会被reset为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的position。
mark和reset
缓冲区的markreset是调用该方法时将其positionreset到的索引。mark并非总是定义的,但是定义mark时,它永远不会为负,也永远不会大于position。如果定义了mark,则在将position或limit调整为小于mark的值时将其丢弃。如果未定义mark,则调用该reset方法 InvalidMarkException将引发。
对于mark,position,limit和capacity,以下不变式成立:
0 <= mark <= position <= limit <= capacity
清除,翻转和倒带
除了访问position,limit和capacity值以及mark和reset的方法外,此类还定义了以下对缓冲区的操作:
clear()使缓冲区为新的通道读取或相对放置操作序列做好准备:将limit设置为capacity,并将position设置为零。
flip()使缓冲区为新的通道写入或相对get操作序列做好准备:将limit设置为当前position,然后将position设置为零。
rewind() 使缓冲区准备好重新读取它已经包含的数据:保留limit不变,并将position设置为零。
线程安全
缓冲区是线程安全的
使用Buffer读写数据一般遵循以下四个步骤:
1.写入数据到Buffer
2.调用flip()方法
3.从Buffer中读取数据
4.调用clear()方法或者compact()方法
写读->写读->写读
当向buffer写入数据时,buffer会记录下写了多少数据。
一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。
在读模式下,可以读取之前写入到buffer的所有数据。
一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。
有两种方式能清空缓冲区:调用clear()或compact()方法。
clear()方法会清空整个缓冲区。
compact()方法只会清除已经读过的数据。
任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
例子:
RandomAccessFile aFile = new RandomAccessFile("file/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);
//从此通道将字节序列读取到缓冲区中,相当于写入,如果 返回值= -1 标识通道已被读取到末尾。
int bytesRead = inChannel.read(buf); //read into buffer.
while (bytesRead != -1) {
/**
* 翻转此缓冲区。将limit设置为当前position,然后将该position设置为零。如果定义了mark,则将其丢弃。
* 在执行一系列通道读取或放置操作之后,调用此方法以准备一系列通道写入或相对 get操作
*/
buf.flip(); //make buffer ready for read
//告诉当前position和极限之间是否有任何元素。 当且仅当此缓冲区中剩余至少一个元素时,才返回true
while(buf.hasRemaining()){
System.out.print((char) buf.get()); // read 1 byte at a time
}
//将缓冲区的position,limit设置初始化状态,不清空数据
buf.clear(); //make buffer ready for writing
//更新通道的状态。 inChannel.read(buf) = -1
bytesRead = inChannel.read(buf);
}
aFile.close();
向Buffer中写数据
写数据到Buffer有两种方式:
- 从Channel写到Buffer。
- 通过Buffer的put()方法写到Buffer里。
从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表示之前写进了多少个byte、char等 —— 现在能读取多少个byte、char等。
从Buffer中读取数据
从Buffer中读取数据有两种方式:
- 从Buffer读取数据到Channel。
- 使用get()方法从Buffer中读取数据。
从Buffer读取数据到Channel的例子:
//read from buffer into channel.
int bytesWritten = inChannel.write(buf);
使用get()方法从Buffer中读取数据的例子
byte aByte = buf.get();
get方法有很多版本,允许你以不同的方式从Buffer中读取数据。例如,从指定position读取,或者从Buffer中读取数据到字节数组。更多Buffer实现的细节参考JavaDoc。
rewind()方法
Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)。
clear()与compact()方法
一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。
如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。
如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。
如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用compact()方法。
compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。
mark()与reset()方法
通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用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相等:
有相同的类型(byte、char、int等)。
Buffer中剩余的byte、char等的个数相等。
Buffer中所有剩余的byte、char等都相同。
如你所见,equals只是比较Buffer的一部分,不是每一个在它里面的元素都比较。实际上,它只比较Buffer中的剩余元素。
compareTo()方法
compareTo()方法比较两个Buffer的剩余元素(byte、char等), 如果满足下列条件,则认为一个Buffer“小于”另一个Buffer:
第一个不相等的元素小于另一个Buffer中对应的元素 。
所有元素都相等,但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。
(译注:剩余元素是从 position到limit之间的元素)
源码分析:
/**
* The characteristics of Spliterators that traverse and split elements
* maintained in Buffers.
*/
static final int SPLITERATOR_CHARACTERISTICS =
Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;
// Invariants: mark <= position <= limit <= capacity
//定义私有属性 标记,默认值为-1
private int mark = -1;
//定义私有属性 位置,默认值为0
private int position = 0;
//定义私有属性 限制
private int limit;
//定义私有属性 容量
private int capacity;
// Used only by direct buffers 仅由缓冲区直接使用
// NOTE: hoisted here for speed in JNI GetDirectBufferAddress
long address;
// 检查不变量后,使用给定mark,position,limit,capacity创建一个新缓冲区
Buffer(int mark, int pos, int lim, int cap) { // package-private
//capacity容量必须大于0
if (cap < 0)
throw new IllegalArgumentException("Negative capacity: " + cap);
this.capacity = cap;
//将设置limit的逻辑封装起来
limit(lim);
//同理
position(pos);
//如果设置了标记mark,则判断是否有mark大于position的情况,若有抛异常。
if (mark >= 0) {
if (mark > pos)
throw new IllegalArgumentException("mark > position: ("
+ mark + " > " + pos + ")");
this.mark = mark;
}
}
/**
* Returns this buffer's capacity.
*
* @return The capacity of this buffer
*/
public final int capacity() {
return capacity;
}
/**
* Returns this buffer's position.
*
* @return The position of this buffer
*/
public final int position() {
return position;
}
/**
* Sets this buffer's position. If the mark is defined and larger than the
* new position then it is discarded.
*
* @param newPosition
* The new position value; must be non-negative
* and no larger than the current limit
*
* @return This buffer
*
* @throws IllegalArgumentException
* If the preconditions on <tt>newPosition</tt> do not hold
* ----------------------
* 设置缓冲区的位置,如果标记已定义且大于新位置,则将其丢弃。
*/
public final Buffer position(int newPosition) {
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
//如果mark大于新位置,则mark被丢弃,设置-1默认值。
if (mark > position) mark = -1;
return this;
}
/**
* Returns this buffer's limit.
*
* @return The limit of this buffer
*/
public final int limit() {
return limit;
}
/**
* Sets this buffer's limit. If the position is larger than the new limit
* then it is set to the new limit. If the mark is defined and larger than
* the new limit then it is discarded.
* @param newLimit
* The new limit value; must be non-negative
* and no larger than this buffer's capacity
*
* @return This buffer
*
* @throws IllegalArgumentException
* If the preconditions on <tt>newLimit</tt> do not hold
* ----------------------
* 设置缓冲区的限制。如果position大于新limit,则将position设置为新limit指向的地方。如果是mark已定义且大于新limit,,则将其丢弃。
*/
public final Buffer limit(int newLimit) {
if ((newLimit > capacity) || (newLimit < 0))
throw new IllegalArgumentException();
limit = newLimit;
if (position > newLimit) position = newLimit;
if (mark > newLimit) mark = -1;
return this;
}
/**
* Sets this buffer's mark at its position.
*
* @return This buffer
*/
public final Buffer mark() {
//将标记设置为当前位置
mark = position;
return this;
}
/**
* Resets this buffer's position to the previously-marked position.
*
* <p> Invoking this method neither changes nor discards the mark's
* value. </p>
*
* @return This buffer
*
* @throws InvalidMarkException
* If the mark has not been set
* ----------------------
* 将该缓冲区的位置reset为先前标记的位置。
* 调用此方法不会改变和丢弃标记的值。
*/
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
/**
* Clears this buffer. The position is set to zero, the limit is set to
* the capacity, and the mark is discarded.
*
* <p> Invoke this method before using a sequence of channel-read or
* <i>put</i> operations to fill this buffer. For example:
*
* <blockquote><pre>
* buf.clear(); // Prepare buffer for reading
* in.read(buf); // Read data</pre></blockquote>
*
* <p> This method does not actually erase the data in the buffer, but it
* is named as if it did because it will most often be used in situations
* in which that might as well be the case. </p>
* ----------------------
* 清除此缓冲区。将position设为0,limit设置capacity的值和丢弃掉mark。
*
* 在使用一系列channel.read() 或 buffer.put()操作之前,请调用此方法。
* For example:
* buf.clear(); //准备缓冲区以读取。
* in.read(buf); //读取数据
*
* 该方法实际上并不会擦除缓冲区中的数据。但他确实最常用于清除的情况,所以有了这个命名
*
* @return This buffer
*/
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
/**
* Flips this buffer. The limit is set to the current position and then
* the position is set to zero. If the mark is defined then it is
* discarded.
*
* <p> After a sequence of channel-read or <i>put</i> operations, invoke
* this method to prepare for a sequence of channel-write or relative
* <i>get</i> operations. For example:
*
* <blockquote><pre>
* buf.put(magic); // Prepend header
* in.read(buf); // Read data into rest of buffer
* buf.flip(); // Flip buffer
* out.write(buf); // Write header + data to channel</pre></blockquote>
*
* <p> This method is often used in conjunction with the {@link
* java.nio.ByteBuffer#compact compact} method when transferring data from
* one place to another. </p>
*
* @return This buffer
*----------------------
* 翻转此缓冲区。limit设置为当前position的值,然后position设为0。如果定义了mark,则将其抛弃。
* 在一系列通道channel.read或buffer.put之后,调用此方法为一系列channel.write或buffer.get操作做准备。
* For example:
* buf.put(magic) //设置缓冲区的header
* in.read(buf); //将数据读入(写入)缓冲区
* buf.flip(); //翻转缓冲区
* out.write(buf); //将header+data写入通道
*
* 当将数据从一个地方传输到另一个地方时,该方法通常与compact方法结合使用。
*/
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
/**
* Rewinds this buffer. The position is set to zero and the mark is
* discarded.
*
* <p> Invoke this method before a sequence of channel-write or <i>get</i>
* operations, assuming that the limit has already been set
* appropriately. For example:
*
* <blockquote><pre>
* out.write(buf); // Write remaining data
* buf.rewind(); // Rewind buffer
* buf.get(array); // Copy data into array</pre></blockquote>
*
* 倒带该缓冲区。position设为0且mark被丢弃。
*
* 假设已经适当设置了限制,请在执行一系列channel.write 或 buffer.get()操作之前调用此方法。
* For example:
* out.write(buf); //将剩余的data写入buffer中
* buf.rewind(); //倒带缓冲区,将position设为0,即可以重读buffer中的所有数据。
* buf.get(array); //从buffer中读取数据到array中
*
*
* @return This buffer
*/
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
/**
* Returns the number of elements between the current position and the
* limit.
* 返回当前position和limit之间的元素个数
* @return The number of elements remaining in this buffer
*/
public final int remaining() {
return limit - position;
}
/**
* Tells whether there are any elements between the current position and
* the limit.
* 告诉当前position和limit之间是否有任何元素。
* @return <tt>true</tt> if, and only if, there is at least one element
* remaining in this buffer
* 当且仅当次缓冲区中至少剩余一个元素就会返回true
*/
public final boolean hasRemaining() {
return position < limit;
}
/**
* Tells whether or not this buffer is read-only.
*
* @return <tt>true</tt> if, and only if, this buffer is read-only
*/
public abstract boolean isReadOnly();
/**
* Tells whether or not this buffer is backed by an accessible
* array.
*
* <p> If this method returns <tt>true</tt> then the {@link #array() array}
* and {@link #arrayOffset() arrayOffset} methods may safely be invoked.
* </p>
*
* @return <tt>true</tt> if, and only if, this buffer
* is backed by an array and is not read-only
*
* @since 1.6
*
* 告诉此缓冲区是否由可访问数组支持。
* 如果此方法返回true,则可以安全地调用array 和arrayOffset方法。
*
*/
public abstract boolean hasArray();
/**
* Returns the array that backs this
* buffer <i>(optional operation)</i>.
*
* <p> This method is intended to allow array-backed buffers to be
* passed to native code more efficiently. Concrete subclasses
* provide more strongly-typed return values for this method.
*
* <p> Modifications to this buffer's content will cause the returned
* array's content to be modified, and vice versa.
*
* <p> Invoke the {@link #hasArray hasArray} method before invoking this
* method in order to ensure that this buffer has an accessible backing
* array. </p>
*
* @return The array that backs this buffer
*
* @throws ReadOnlyBufferException
* If this buffer is backed by an array but is read-only
*
* @throws UnsupportedOperationException
* If this buffer is not backed by an accessible array
*
* @since 1.6
*
* 返回支持此缓冲区的数组 (可选操作)。
* 此方法旨在使支持数组的缓冲区更有效地传递给本机代码。具体的子类为此方法提供了更* 强类型的返回值。
* 对该缓冲区内容的修改将导致返回数组的内容被修改,反之亦然。
* 调用hasArray为了调用此方法,以确保这个缓冲器具有可访问的底层阵列之前方法。
*/
public abstract Object array();
/**
* Returns the offset within this buffer's backing array of the first
* element of the buffer <i>(optional operation)</i>.
*
* <p> If this buffer is backed by an array then buffer position <i>p</i>
* corresponds to array index <i>p</i> + <tt>arrayOffset()</tt>.
*
* <p> Invoke the {@link #hasArray hasArray} method before invoking this
* method in order to ensure that this buffer has an accessible backing
* array. </p>
*
* @return The offset within this buffer's array
* of the first element of the buffer
*
* @throws ReadOnlyBufferException
* If this buffer is backed by an array but is read-only
*
* @throws UnsupportedOperationException
* If this buffer is not backed by an accessible array
*
* @since 1.6
* 返回此缓冲区的第一个元素在此缓冲区的后备数组内的偏移量 (可选操作)。
* 如果此缓冲区由数组支持,则缓冲区位置p 对应于数组索引p + arrayOffset()。
*
* 调用hasArray为了调用此方法,以确保这个缓冲器具有可访问的底层阵列之前方法。
*/
public abstract int arrayOffset();
/**
* Tells whether or not this buffer is
* <a href="ByteBuffer.html#direct"><i>direct</i></a>.
*
* @return <tt>true</tt> if, and only if, this buffer is direct
*
* @since 1.6
* 告诉此缓冲区是否是直接缓冲区。
* true 直接缓冲区
*/
public abstract boolean isDirect();
// -- Package-private methods for bounds checking, etc. --
/**
* Checks the current position against the limit, throwing a {@link
* BufferUnderflowException} if it is not smaller than the limit, and then
* increments the position.
*
* @return The current position value, before it is incremented
*/
final int nextGetIndex() { // package-private
int p = position;
if (p >= limit)
throw new BufferUnderflowException();
position = p + 1;
return p;
}
final int nextGetIndex(int nb) { // package-private
int p = position;
if (limit - p < nb)
throw new BufferUnderflowException();
position = p + nb;
return p;
}
/**
* Checks the current position against the limit, throwing a {@link
* BufferOverflowException} if it is not smaller than the limit, and then
* increments the position.
*
* @return The current position value, before it is incremented
*/
final int nextPutIndex() { // package-private
int p = position;
if (p >= limit)
throw new BufferOverflowException();
position = p + 1;
return p;
}
final int nextPutIndex(int nb) { // package-private
int p = position;
if (limit - p < nb)
throw new BufferOverflowException();
position = p + nb;
return p;
}
/**
* Checks the given index against the limit, throwing an {@link
* IndexOutOfBoundsException} if it is not smaller than the limit
* or is smaller than zero.
*/
final int checkIndex(int i) { // package-private
if ((i < 0) || (i >= limit))
throw new IndexOutOfBoundsException();
return i;
}
final int checkIndex(int i, int nb) { // package-private
if ((i < 0) || (nb > limit - i))
throw new IndexOutOfBoundsException();
return i;
}
final int markValue() { // package-private
return mark;
}
final void truncate() { // package-private
mark = -1;
position = 0;
limit = 0;
capacity = 0;
}
final void discardMark() { // package-private
mark = -1;
}
static void checkBounds(int off, int len, int size) { // package-private
if ((off | len | (off + len) | (size - (off + len))) < 0)
throw new IndexOutOfBoundsException();
}
}
本文深入探讨Java NIO Buffer的工作原理,包括其基本属性、操作方法及如何在读写数据时使用。涵盖Buffer的mark、reset、clear、flip和rewind方法的详细解释。
790

被折叠的 条评论
为什么被折叠?



