Java NIO之Buffer
缓冲区基础
本质上,缓冲区是就是一个数组。所有的缓冲区都具有四个属性来提供关于其所包含的数组的信息。它们是:
- 容量(Capacity) 缓冲区能够容纳的数据元素的最大数量。容量在缓冲区创建时被设定,并且永远不能被改变。
- 上界(Limit) 缓冲区里的数据的总数,代表了当前缓冲区中一共有多少数据。
- 位置(Position) 下一个要被读或写的元素的位置。Position会自动由相应的 get( )和 put( )函数更新。
- 标记(Mark) 一个备忘位置。用于记录上一次读写的位置。一会儿,我会通过reset方法来说明这个属性的含义。
我们以字节缓冲区为例,ByteBuffer是一个抽象类,不能直接通过 new 语句来创建,只能通过一个static方法 allocate 来创建:
ByteBuffer byteBuffer = ByteBuffer.allocate(256);
以上的语句可以创建一个大小为256字节的ByteBuffer,此时,mark = -1, pos = 0, limit = 256, capacity = 256。capacity在初始化的时候确定了,运行时就不会再变化了,而另外三个变量是随着程序的执行而不断变化的。
缓冲区的存取
缓冲区用于存取的方法定义主要是put , get。
put 方法有多种重载,我们看其中一个:
public ByteBuffer put(byte x) {
hb[ix(nextPutIndex())] = x;
return this;
}
final int nextPutIndex() { // package-private
if (position >= limit)
throw new BufferOverflowException();
return position++;
}
这个方法是把一个byte变量 x 放到缓冲区中去。position会加1。再来看一下get方法,也是从position的位置去取缓冲区中的一个字节:
···
public byte get() {
return hb[ix(nextGetIndex())];
}
final int nextGetIndex() { // package-private
if (position >= limit)
throw new BufferUnderflowException();
return position++;
}
···
那比如,我要是想在一个Buffer中放入了数据,然后想从中读取的话,就要把position调到我想读的那个位置才行。为此,ByteBuffer上定义了一个方法:
public final Buffer position(int newPosition) {
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
if (mark > position) mark = -1;
return this;
}
这里面用到了limit,想一下上面的定义,limit代表可写或者可读的总数。一个新创建的bytebuffer,它可写的总数就是它的capacity。如果写入了一些数据以后,想从头开始读的话,这时候的limit应该就是当前ByteBuffer中数据的总长度。下面的这个图比较直观地说明了这个问题:
为了达到从写数据的情况变成读数据的情况,还需要修改limit,这就要用到limit方法:
public final Buffer limit(int newLimit) {
if ((newLimit > capacity) || (newLimit < 0))
throw new IllegalArgumentException();
limit = newLimit;
if (position > limit) position = limit;
if (mark > limit) mark = -1;
return this;
}
我们可以这样写,就把byteBuffer从读变成写了: byteBuffer.limit(byteBuffer.position())
byteBuffer.position(0);
当然,由于这个操作非常频繁,jdk就为我们封装了一个这样的方法,叫做flip:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
使用这个反转方法,思路一定要清晰,稍有不慎,就会带来莫名其妙的错误,比如,连续调用flip会对ByteBuffer有什么样的影响呢?这其实会使得Buffer的limit变成0,从而既不能读也不能写了。
limit的设计确实可以加速数据溢出情况的检查,但是造成使用上和理解上的困难,我还是觉得得不偿失。我一直觉得limit这个设计很蠢(个人意见,如果有误,请各位指正)。
缓冲区标记
今天最后一个内容,是讲一下mark的作用。在理解了position的作用以后,mark就很容易理解了,它就是记住当前的位置用的:
public final Buffer mark() {
mark = position;
return this;
}
我们在调用过mark以后,再进行缓冲区的读写操作,position就会发生变化,为了再回到当初的位置,我们可以调用reset方法恢复position的值:
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
其他方法
- clear
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
- rewind
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
- remaining
public final int remaining() {
return limit - position;
}
- hasRemaining
public final boolean hasRemaining() {
return position < limit;
}
文章转自:https://zhuanlan.zhihu.com/p/27296046;并加了一些自己的东西。
下一篇: Java Api之IO流输入与输出