NIO的Buffer类
NIO的Buffer(缓冲区)本质上是一个内存块,既可以写入数据,也可以从中读取数据。 Java NIO中代表缓冲区的Buffer类是一个抽象类,位于java.nio包中。 NIO的Buffer的内部是一个内存块(数组),此类与普通的内存块(Java数组)不同的 是:NIO Buffer对象,提供了一组比较有效的方法,用来进行写入和读取的交替访问。
package java.nio;
import jdk.internal.HotSpotIntrinsicCandidate;
import jdk.internal.access.JavaNioAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.Unsafe;
import java.util.Spliterator;
public abstract class Buffer {
// Cached unsafe-access object
static final Unsafe UNSAFE = Unsafe.getUnsafe();
/**
* The characteristics of Spliterators that traverse and split elements
* maintained in Buffers.
*/
static final int SPLITERATOR_CHARACTERISTICS =
Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;
//重要属性
private int mark = -1; //读写位置的临时备份
private int position = 0; //读写位置
private int limit; //读写的限制
private int capacity; //读写位置的临时备份
long address;
Buffer(int mark, int pos, int lim, int cap) { // package-private
if (cap < 0)
throw createCapacityException(cap);
this.capacity = cap;
limit(lim);
position(pos);
if (mark >= 0) {
if (mark > pos)
throw new IllegalArgumentException("mark > position: ("
+ mark + " > " + pos + ")");
this.mark = mark;
}
}
static IllegalArgumentException createSameBufferException() {
return new IllegalArgumentException("The source buffer is this buffer");
}
static IllegalArgumentException createCapacityException(int capacity) {
assert capacity < 0 : "capacity expected to be negative";
return new IllegalArgumentException("capacity < 0: ("
+ capacity + " < 0)");
}
public final int capacity() {
return capacity;
}
public final int position() {
return position;
}
public Buffer position(int newPosition) {
if (newPosition > limit | newPosition < 0)
throw createPositionException(newPosition);
if (mark > newPosition) mark = -1;
position = newPosition;
return this;
}
private IllegalArgumentException createPositionException(int newPosition) {
String msg = null;
if (newPosition > limit) {
msg = "newPosition > limit: (" + newPosition + " > " + limit + ")";
} else { // assume negative
assert newPosition < 0 : "newPosition expected to be negative";
msg = "newPosition < 0: (" + newPosition + " < 0)";
}
return new IllegalArgumentException(msg);
}
public final int limit() {
return limit;
}
public Buffer limit(int newLimit) {
if (newLimit > capacity | newLimit < 0)
throw createLimitException(newLimit);
limit = newLimit;
if (position > newLimit) position = newLimit;
if (mark > newLimit) mark = -1;
return this;
}
private IllegalArgumentException createLimitException(int newLimit) {
String msg = null;
if (newLimit > capacity) {
msg = "newLimit > capacity: (" + newLimit + " > " + capacity + ")";
} else { // assume negative
assert newLimit < 0 : "newLimit expected to be negative";
msg = "newLimit < 0: (" + newLimit + " < 0)";
}
return new IllegalArgumentException(msg);
}
public Buffer mark() {
mark = position;
return this;
}
public Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
public Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
public Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
public Buffer rewind() {
position = 0;
mark = -1;
return this;
}
public final int remaining() {
int rem = limit - position;
return rem > 0 ? rem : 0;
}
public final boolean hasRemaining() {
return position < limit;
}
public abstract boolean isReadOnly();
public abstract boolean hasArray();
public abstract Object array();
public abstract int arrayOffset();
public abstract boolean isDirect(
public abstract Buffer slice();
public abstract Buffer duplicate();
// -- Package-private methods for bounds checking, etc. --
abstract Object base();
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;
}
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;
}
@HotSpotIntrinsicCandidate
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();
}
static {
// setup access to this package in SharedSecrets
SharedSecrets.setJavaNioAccess(
new JavaNioAccess() {
@Override
public JavaNioAccess.BufferPool getDirectBufferPool() {
return Bits.BUFFER_POOL;
}
@Override
public ByteBuffer newDirectByteBuffer(long addr, int cap, Object ob) {
return new DirectByteBuffer(addr, cap, ob);
}
@Override
public void truncate(Buffer buf) {
buf.truncate();
}
});
}
}
NIO Buffer的四个属性
//重要属性
private int mark = -1; //读写位置的临时备份
private int position = 0; //读写位置
private int limit; //读写的限制
private int capacity; //读写位置的临时备份
-
capacity表示内部容量的大小。
- 写入的对象数量超过了capacity容量,缓冲区就满了,不能再写入了。
- 初始化后不能再更改,初始化时,会按照capacity分配内部数组的内存,在数组内存分配好之后,它的大小就不能改变。
-
position表示当前的位置
position属性的值与缓冲区的读写模式有关。在缓冲区进行读写的模式改变时,position值会进行相应的调整。
读/写模式下规则:
- 在刚进入到读取/写入模式时,position值为0,表示当前的读取/写入位置为从头开始。
- 每当一个数据读取/写入到缓冲区之后,position会向后移动到下一个可读取/写入的位置。
- 初始的position值为0,最大可读取/写入值为limit–1。当position值达到limit时,缓冲区就已经无空间可读取/写入了。
写入模式到读取模式的flip翻转过程中,position和limit属性值会进行调整,具体的 规则是:
- limit属性被设置成写入模式时的position值,表示可以读取的最大数据位置;
- position由原来的写入位置,变成新的可读位置,也就是0,表示可以从头开始读;
- 在从缓冲区中读取数据完毕后,limit的值仍然保持在我们调用flip()方法时的值,调用clear()方法能够把所有的状态变化设置为初始化时的值。
-
limit属性
limit属性表示可以写入或者读取的最大上限
-
mark属性
mark属性的主要作用就是为读位置或者写位置的索引做一个备份。在缓冲区操作(读或者写)的过程当中,可以将当前的position的值,临时存入mark属性中;需要恢复的时候,可以再从mark中取出之前的值,恢复到position属性中,然后,后续可以重新从position位置开始处理(读取或者写入)。
四个属性就介绍完了,看起来可能会有一点抽象,其实很好理解。
下面画图描述一下整个过程:
position、limit、capacity三个属性值之间有一些相对大小的关系:
0 <= position <= limit <= capacity
如果我们创建一个新的容量大小为10的ByteBuffer对象,在初始化的时候,position设置为0,limit和 capacity被设置为10,在以后使用ByteBuffer对象过程中,capacity的值不会再发生变化,而其它两个将会随着使用而变化。三个属性值分别如图所示:
现在我们可以从通道中读取一些数据到缓冲区中,注意从通道读取数据,相当于往缓冲区中写入数据。如果读取5个字节的数据,则此时position的值为5,即下一个将要被写入的字节索引为5,而limit仍然是10,如下图所示:
下一步把读取的数据写入到输出通道中,相当于从缓冲区中读取数据,在此之前,必须调用**flip( )**方法,该方法将会完成两件事情:
- 把limit设置为当前的position值
- 把position设置为0
由于position被设置为0,所以可以保证在下一步输出时读取到的是缓冲区中的第一个字节,而limit被设置为当前的position,可以保证读取的数据正好是之前写入到缓冲区中的数据,如下图所示:
现在调用get()方法从缓冲区中读取数据写入到输出通道,这会导致position的增加而limit保持不变,但position不会超过limit的值,所以在读取我们之前写入到缓冲区中的5个字节之后,position和limit的值都为5,如下图所示:
在从缓冲区中读取数据完毕后,limit的值仍然保持在我们调用flip()方法时的值,调用clear()方法能够把所有的状态变化设置为初始化时的值,如下图所示:
最后我们用一段代码来验证这个过程,如下所示:
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class Program {
public static void main(String args[]) throws Exception {
FileInputStream fin = new FileInputStream("d:\\test.txt");
FileChannel fc = fin.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(10);
output("初始化", buffer);
fc.read(buffer);
output("调用read()", buffer);
buffer.flip();
output("调用flip()", buffer);
while (buffer.remaining() > 0) {
byte b = buffer.get();
// System.out.print(((char)b));
}
output("调用get()", buffer);
buffer.clear();
output("调用clear()", buffer);
fin.close();
}
public static void output(String step, Buffer buffer) {
System.out.println(step + " : ");
System.out.print("capacity: " + buffer.capacity() + ", ");
System.out.print("position: " + buffer.position() + ", ");
System.out.println("limit: " + buffer.limit());
System.out.println();
}
}
了解了这个过程那么,我们再来看NIO Buffer类的重要方法。
NIOBuffer类有哪些方法?
- allocate()创建缓冲区
在使用Buffer(缓冲区)实例之前,我们首先需要获取Buffer子类的实例对象,并且分 配内存空间。如果需要获取一个Buffer实例对象,并不是使用子类的构造器来创建一个实例 对象,而是调用子类的allocate()方法。
看下源码(ByteBuffer子类的):
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw createCapacityException(capacity);
//申请堆内存创建缓冲区
return new HeapByteBuffer(capacity, capacity);
}
//申请直接内存的方式创建缓冲区
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
- put()写入缓冲区
在调用allocate方法分配内存、返回了实例对象后,缓冲区实例对象处于写模式,可以 写入对象,而如果要写入对象到缓冲区,需要调用put方法。put方法很简单,只有一个参数, 即为所需要写入的对象。只不过,写入的数据类型要求与缓冲区的类型保持一致。
//抽象类,由具体缓冲区类进行操作
public abstract ByteBuffer put(byte b);
可以看到这里有四个缓冲区的实现类具体put方法在这些类调用
- flip()翻转
flip()翻转方法是Buffer类提供的一个模式转变的重要方法,它的作用就是将写入模式翻转成读取模式。看一下源码实现,很简单,就是把position的值给limit,position设置为0.mark为-1.
public Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
- get()从缓冲区读取
读取数据的方法很简单,可以调用get方法每次从position的位置读取一个数据,并且进 行相应的缓冲区属性的调整。跟put类似,是抽象类具体实现在DirectByteBuffer或HeapByteBuffer里。
// -- Singleton get/put methods --
/**
* Relative <i>get</i> method. Reads the byte at this buffer's
* current position, and then increments the position.
*
* @return The byte at the buffer's current position
*
* @throws BufferUnderflowException
* If the buffer's current position is not smaller than its limit
*/
public abstract byte get();
- rewind()倒带
已经读完的数据,如果需要再读一遍,可以调用rewind()方法。rewind()也叫倒带,就像 播放磁带一样倒回去,再重新播放。
public Buffer rewind() {
position = 0;
mark = -1;
return this;
}
rewind ()方法,主要是调整了缓冲区的position属性与mark标记属性,具体的调整规则如 下: (1)position重置为0,所以可以重读缓冲区中的所有数据; (2)limit保持不变,数据量还是一样的,仍然表示能从缓冲区中读取的元素数量; (3)mark标记被清理,表示之前的临时位置不能再用了。
- mark( )和reset( )
mark( )和reset( )两个方法是成套使用的:Buffer.mark()方法将当前position的值保存起来, 放在mark属性中,让mark属性记住这个临时位置;之后,可以调用Buffer.reset()方法将mark 的值恢复到position中。
/**
* Sets this buffer's mark at its position.
*
* @return This buffer
*/
public 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
*/
public Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
- clear( )清空缓冲区
在读取模式下,调用clear()方法将缓冲区切换为写入模式。此方法的作用: (1)会将position清零; (2)limit设置为capacity最大容量值,可以一直写入,直到缓冲区写满。
/**
* 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>
*
* @return This buffer
*/
public Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
最后总结一下Buffer类的使用流程
(1)使用创建子类实例对象的allocate( )方法,创建一个Buffer类的实例对象。 (2)调用put( )方法,将数据写入到缓冲区中。 (3)写入完成后,在开始读取数据前,调用Buffer.flip( )方法,将缓冲区转换为读模式。 (4)调用get( )方法,可以从缓冲区中读取数据。 (5)读取完成后,调用Buffer.clear( )方法或Buffer.compact()方法,将缓冲区转换为写入 模式,可以继续写入。