Java NIO之Buffer详细理解
Buffer**简介**
Buffer:是一个指定特定数据类型的容器,主要用于和Channel进行数据交互。在多线程操作下 Buffer 是不安全的。
在Java NIO中使用的核心缓冲区如下(覆盖了通过I/O发送的基本数据类型:byte, char、short, int, long, float, double, long):
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
FloatBuffer
DoubleBuffer
LongBuffer
Buffer的基本属性
capacity(容量) :缓冲区能够容纳的数据元素的最大数量。
limit(界限):缓冲区中第一个不能读或写的元素位置
position(位置):下一个读或写的元素的索引。位置会随着get()和put()函数更新
mark(标记):标记一个备忘的位置
以上四个属性的关系:
mark <= position <= limit <= capacity
Buffer**使用**
接下来以最常用的ByteBuffer为例进行介绍
1.创建Buffer对象
public static ByteBuffer allocate(int capacity) //静态方法,通过内存分配创建Buffer对象
public static ByteBuffer wrap(byte[] array) //静态方法,将[]byte包装成ByteBuffer对象
public static ByteBuffer wrap(byte[] array,int offset, int length) //基本同上,但指定了初始位置和长度
public static ByteBuffer allocateDirect(int capacity) //通过分配直接缓冲区创建Buffer对象
非直接缓冲区:通过allocate()分配缓冲区,将缓冲区建立在JVM的内存中
直接缓冲区:通过allocateDirect()分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率。
内核地址空间和用户地址空间之间形成了一个物理内存映射文件,减少了内核到用户空间的copy过程
public abstract boolean isDirect() //可以通过该方法判断是否是直接缓冲区
2.读写常用的方法
在Buffer中有两种模式,一种是写模式,一种是读模式。
put()相关函数:向ByteBuffer中添加元素(byte,[]byte,ByteBuffer等)
flip()函数:将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。
get():从Buffer中读取元素
hasRemaining():判断Buffer中是否还有元素可读
clear():清空元素
compact():压缩元素(扩展空间)
利用Buffer读写数据,通常遵循四个步骤:
调用put()把数据写入buffer;
调用flip()从写模式切换成读模式;
调用get()从Buffer中读取数据;
调用buffer.clear()或者buffer.compact() 清除元素
Buffer**源码**
构造器
//构造函数,根据指定的参数来初始化Buffer特定的属性
//此构造函数是包私有的
Buffer(int mark, int pos, int lim, int cap) { // package-private
if (cap < 0)
throw new IllegalArgumentException("Negative capacity: " + cap);
this.capacity = cap;
limit(lim);
position(pos);
if (mark >= 0) {
if (mark > pos)
throw new IllegalArgumentException("mark > position: ("
+ mark + " > " + pos + ")");
this.mark = mark;
}
}
构造器中使用了两个方法limit()和position()
函数的功能:设置Buffer的limit,如果position大于newLimit,则将position设置为新的limit。
如果mark被定义且大于新的limit,则就会被抛弃。
public final Buffer limit(int newLimit) {
//有效性检查,即limit必须满足这样的关系:0<=limit<=position.
if ((newLimit > capacity) || (newLimit < 0))
throw new IllegalArgumentException();
limit = newLimit;
//如果position大于newLimit,则将position设置为新的limit。
if (position > limit) position = limit;
//如果mark被定义且大于新的limit,则会被抛弃(即设置为-1)
if (mark > limit) mark = -1;
return this;
}
函数功能:设置Buffer的position.如果mark被定义且大于new position,则就会被抛弃。
public final Buffer position(int newPosition) {
//有效性检查,即0<=newPosition<=limit.
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
//如果mark被定义且大于new position,则就会被抛弃。
if (mark > position) mark = -1;
return this;
}
allocate()方法
由于Buffer类是一个抽象类,是不可以实例化对象的,因此在Buffer中是不存在allocate(int cap)方法的,allocate(int cap)方法在其子类中均有实现。这里就以IntBuffer为例,看下Buffer子类IntBuffer的allocate(int cap)方法。
public static IntBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapIntBuffer(capacity, capacity);
}
函数功能:分配一个新的用来装载int类型数据的Buffer对象实例。这个new buffer的position为0,limit为capacity,mark为未定义的(即为-1)。buffer中的元素全部初始化为零。
在allocate方法中直接是实例化了一个IntBuffer子类的对象。
既然这里涉及要HeapIntBuffer类,我们就看下这个类
public abstract class IntBuffer extends Buffer implements Comparable<IntBuffer>
class HeapIntBuffer extends IntBuffer
从继承关系我们知道:HeapIntBuffer这个类是IntBuffer的子类
这个类的构造函数为
HeapIntBuffer(int cap, int lim) { // package-private
super(-1, 0, lim, cap, new int[cap], 0);
}
在这个构造函数中直接调用了父类IntBuffer中对应的构造函数。看下IntBuffer类中的这个构造函数。
在看构造函数之前,这里要说下IntBuffer类中的几个属性。
final int\[\] hb; // Non-null only for heap buffers
final int offset;
boolean isReadOnly; // Valid only for heap buffers
IntBuffer类中主要包括一个int类型的数组,即从这里我们知道,Buffer类的底层数据结构是借助于数组来完成的。
继续看IntBuffer类的构造方法
// Creates a new buffer with the given mark, position, limit, capacity,
// backing array, and array offset
IntBuffer(int mark, int pos, int lim, int cap, // package-private
int[] hb, int offset)
{
super(mark, pos, lim, cap);//调用父类Buffer中对应有参的构造函数
this.hb = hb;
this.offset = offset;
}
以上,就是当我们使用如下代码得到IntBuffer实例的整个过程。
IntBuffer buffer = IntBuffer.allocate(cap);
2、put(int i)方法介绍
下面来看IntBuffer类中的put方法
public abstract IntBuffer put(int i);
public abstract int get(int index);
在IntBuffer类中put、get方法都是抽象的。
有了上面allocate方法的过程分析,我们知道IntBuffer buffer = IntBuffer.allocate(cap)
中的buffer实际上是父类的引用指向的是子类的对象。当我们使用buffer.put(value)/buffer.get().
实际上是调用子类HeapIntBuffer类中的put/get方法,这就是多态。在Java中相当重要的一个特征。
HeapIntBuffer类中put方法的实现如下:
public IntBuffer put(int x) {
hb[ix(nextPutIndex())] = x;
return this;
}
put方法实现的思想就是:将值存储在position位置即可。
这里涉及到两个新的函数,分别为:
1、nextPutIndex(),函数功能:简单来说就是返回下一个要写入元素的索引位置(即当前position值),并将position进行加一操作。
2、ix(int i):偏移offset个位置
这两个函数的实现如下:
//函数功能:首先检查当前的position是否小于limit,如果小于则存储,否则抛异常。
final int nextPutIndex() { // package-private
if (position >= limit)
throw new BufferOverflowException();
return position++;
}
//函数功能:偏移offset个位置
protected int ix(int i) {
return i + offset;
}
3、get()方法介绍
看完了put方法,接下来来看下get方法
函数的功能:取得Buffer中position位置所指向的元素。
//函数功能:获取buffer中position所指向的元素。
public int get() {
return hb[ix(nextGetIndex())];
}
//函数功能:获取buffer中索引为i位置的元素
public int get(int i) {
return hb[ix(checkIndex(i))];
}
在get()方法中也涉及到两个另外的函数:
1、nextGetIndex(),函数功能:返回下一个读取的元素的索引位置(即position值)
2、ix(int i)
final int nextGetIndex() { // package-private
if (position >= limit)
throw new BufferUnderflowException();
return position++;
}
//将position向右移动nb个位置,这个函数目前还没有看见在哪里得到的应用
final int nextGetIndex(int nb) { // package-private
if (limit - position < nb)
throw new BufferUnderflowException();
int p = position;
position += nb;
return p;
}
以上就是get方法的内部实现,也相当简单哈。
4、flip()方法介绍
其实,在刚开始看别人博客的时候,是最不能理解这个函数的功能的,疑问在于:为什么要在读Buffer中的内容时,要先调用这个方法呢。
其实在自己把Buffer的读模式和写模式弄清楚之后,这个函数的功能我也就明白了。
//函数功能:将Buffer从写模式转化为读模式。
//具体为:将limit设置为此时position并且将position设置为零。如果mark被定义了则被抛弃(即设置为-1)
//这个方法的应用场景为:在管道读或调用put方法之后,调用此方法来为管道写或者是get操作做准备。
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
以上就是flip()方法的介绍,是不是也比较简单哈。
5、hasRemaining()介绍
函数功能:判断Buffer中是否还有可读取的元素。
public final boolean hasRemaining() {
return position < limit;
}
此方法的内部实现直接是判断position是否小于limit.
所以,逻辑也是相当简单的。
6、clear() compact()方法的介绍
clear()函数功能:清空Buffer中所有的元素。
其内部实现直接是将Buffer里面几个属性进行了重置。
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
最后看下compact方法
compact()方法的功能:将已读元素进行清除,未读元素拷贝保留并拷贝到Buffer最开始位置。这个也是和clear()方法不同的地方。
具体实现如下:
public IntBuffer compact() {
//先进行拷贝,即将剩余的没有访问的元素拷贝到Buffer从零开始的位置
System.arraycopy(hb, ix(position()), hb, ix(0), remaining());//remaining()函数返回的是剩余元素个数
//设置position为下一个写入元素的位置
position(remaining());
//设置limit为Buffer容量
limit(capacity());
discardMark();//废弃mark,即将mark设置为-1
return this;
}
public final int remaining() {
return limit - position;
}
以上内容主要参考
《Java NIO》
https://blog.csdn.net/u010412719/article/details/52775637
https://blog.csdn.net/u010853261/article/details/53464397
https://www.cnblogs.com/KKSJS/p/9909587.html
https://www.cnblogs.com/snailclimb/p/Buffer.html
还没有评论,来说两句吧...