Java NIO的主要读写处理逻辑就是将数据从通道读入缓冲区,从缓冲区写入到通道中。而这个数据缓冲区的基类就是Buffer。而Buffer本质上就是一块可读写数据的内存,其提供了一些方法,方便外部调用者访问这块内存进行数据读写操作。
使用Buffer读写数据的主要步骤,大体如下:
1、将数据写入Buffer;
2、调用flip()方法,将读写模式由写模式切换成读模式;
3、从Buffer中读取数据;
4、调用clear()方法或者compact()方法清空缓冲区,完成数据读操作。
Buffer中三个十分重要的属性
1、capacity
代表了Buffer的最大容量,标识出Buffer中最多可以存储的capacity个byte、long、char等类型的数据;
2、position
代表了缓冲区Buffer当前待写入或待读取位置,初始值为0,当一个byte、long、char等数据被写入或者被读取后,position自动移动到下一个可写入位置,其最大值为capacity – 1,实际上它受limit限制。一般情况下,Buffer从写模式切换到读模式或者从读模式切换到写模式,position均会被重置为0;
3、limit
表示可写入或可读取数据的限制。在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据,limit等于Buffer的capacity。在读模式下,limit表示你最多能读到多少数据,当切换Buffer到读模式时,limit会被设置成写模式下的position值,也就是说你能读到之前写入的所有数据。
Buffer中,获取这些属性的方法分别是:capacity()、position()、limit()方法,而position、limit也有对应方法进行设置,下面的源码分析我们会分别介绍。
Byffer中,一个不可变的定律是:mark <= position <= limit <= capacity。
初始情况下:
mark = -1
position = 0
limit = capacity - 1
capacity = m
写入n个数据时:
mark = -1
position = n
limit = capacity - 1
capacity = m(m >= n)
flip()切换写模式至读模式时:
mark = -1
position = 0
limit = position(也就是n)
capacity = m(m >= n)
读取k个数据时
mark = -1
position = k
limit = position(也就是n)
capacity = m(m >= n)
Buffer有两种模式:读模式和写模式。这两种模式的切换是是通过以下方法完成的:
1、写模式到读模式:flip()
2、读模式到写模式:clear()或compact()(compact()方法为ByteBuffer中的抽象方法)
Buffer源码分析:
flip()方法
/**
* 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.
* 翻转这个缓冲区。limit被设置为当前位置position,而当前位置position被设置为0。如果标记mark被定义,此时它将被丢弃。
*
* <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:
* 在一系列管道读channel-read或写入put操作之后,调用该方法为一系列管道写channel-write或相关读取get操作做准备。比如:
*
* <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>
*
* 调用buf的put()方法预先放入header数据;
* 调用输入流的read()方法读取数据并写入到缓冲区;
* 翻转buffer:调用buf的flip()将buf由写模式切换到读模式;
* 调用输出流的write()方法将数据从缓冲区中读取到输出流;
*
* <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>
* 当将数据从一个地方转移到另一个地方时,这个方法往往是结合ByteBuffer的compact()方法使用。
*
* @return This buffer
*/
public final Buffer flip() {
// 将limit设置为当前位置position
limit = position;
// 当前位置position设置为0
position = 0;
// 标记mark设置为-1,即无效
mark = -1;
// 返回当前Buffer实例
return this;
}
flip()实际上是翻转数据缓冲区Buffer,将其由写模式转换成读模式。大体逻辑如下:
1、将limit设置为当前位置position,标识出读模式下最大可读取数据限制为之前已写入数据;
2、当前位置position设置为0,标识可读取数据的起始位置;
3、标记mark设置为-1,即无效;
4、返回当前Buffer实例。
clear()方法
/**
* Clears this buffer. The position is set to zero, the limit is set to
* the capacity, and the mark is discarded.
* 清空这个缓冲区buffer。当前位置position被设置为0,读写限制被设置成缓冲区buffer的最大容量capacity,标记mark被设置为无效。
*
* <p> Invoke this method before using a sequence of channel-read or
* <i>put</i> operations to fill this buffer. For example:
* 在使用一系列管道读或者写数据put操作填充该缓冲区buffer前调用该方法。比如:
*
* <blockquote><pre>
* buf.clear(); // Prepare buffer for reading
* in.read(buf); // Read data</pre></blockquote>
* 调用缓冲区buf的clear()方法为管道读并写入数据做准备;
* 调用输入流in的read()方法将数据从管道读出并写入到缓冲区buf;
*
* <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>
* 这个方法并不实际删除缓冲区中的数据
*
* @return This buffer
*/
public final Buffer clear() {
// 当前位置position设置为0
position = 0;
// 读写限制limit设置为buffer的最大容量capacity
limit = capacity;
// 标记mark设置为-1,即无效
mark = -1;
// 返回当前Buffer实例
return this;
}
clear()方法看上去像是清空缓冲区buffer,但是这个方法并不实际删除缓冲区中的数据,而是将其由读模式转换成写模式。它的主要逻辑是:
1、当前位置position设置为0,又可以从0开始写入数据;
2、读写限制limit设置为buffer的最大容量capacity,即我们可以写入数据至完全填充整个缓冲区buffer;
3、标记mark设置为-1,即无效;
4、返回当前Buffer实例。
position()方法
/**
* Returns this buffer's position. </p>
* 返回当前缓冲区buffer的当前可写或可读位置position
*
* @return The position of this buffer
*/
public final int position() {
return position;
}
position()方法用于获取当前缓冲区buffer的当前可写或可读位置position。
position(int newPosition)方法
/**
* Sets this buffer's position. If the mark is defined and larger than the
* new position then it is discarded. </p>
* 设置当前缓冲区buffer的当前可写或可读位置position。如果标记mark被定义,而且大于需要被设置的新位置newPosition,那么它会被标记为无效。
*
* @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) {
// 如果需要被设置的新位置newPosition大于limit或者小于0,直接抛出参数不合法IllegalArgumentException异常
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
// 当前位置position被设置为newPosition
position = newPosition;
// 如果标记mark大于newPosition,标记mark被设置为-1,即无效
if (mark > position) mark = -1;
// 返回当前缓冲区buffer实例this
return this;
}
rewind()方法
一般情况下,flip只能被调用一次,如果是数据需要被重新读入,怎么办?这时rewind()方法就被派上用场了,其代码如下:
/**
* Rewinds this buffer. The position is set to zero and the mark is
* discarded.
* 重绕(倒回)这个缓冲区buffer。当前位置position被设置为0,标记mark被设置为无效。
*
* <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:
* 假设限制limit已经被正确设置,在一系列管道写channel-write或读取get操作之前调用该方法。比如:
*
* <blockquote><pre>
* out.write(buf); // Write remaining data
* buf.rewind(); // Rewind buffer
* buf.get(array); // Copy data into array</pre></blockquote>
* 从缓冲区buf中读取数据并写入输出流out;
* 调用缓冲区buf的rewind()方法,重绕这个缓冲区
* 将缓冲区buf中数据获取,复制到arrary
*
* @return This buffer
*/
public final Buffer rewind() {
// 当前位置position被设置为0
position = 0;
// 标记mark被设置为无效
mark = -1;
// 返回当前缓冲区buffer实例this
return this;
}