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; }