JAVA:NIO中的缓冲区(Buffer)详解 -------源码

                                          缓冲区buffer

1.作用

buffer是一个抽象类,存在于 java.nio包中,在java NIO中负责数据的存取,缓冲区(buffer)就是数组,用于存储不同数据类型的数据,根据数据类型不同(boolean除外),提供了相应类型的缓冲区。ByteBuffer,shortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer通allocate方法获取缓冲区。

2.缓冲区数组的特性

    下面以charbuffer为例从源码角度分析缓冲区数组特性;

2.1四大核心属性

    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;

 (1)mark:标记表示记录当前pos的位置,可以通过reset恢复到mark位置;

(2)位置position:下一个要被读或写的元素索引。位置会自动随调用get()和put()函数更新

(3)上界limit:缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数。

(4)容量capacity:缓冲区能够容纳的数据元素的最大容量。这一容量在缓冲区创建时被设定,就永远不能修改。

一般情况四个属性遵循一下关系:

0<=mark<=position<=limit<=capacity

2.2创建ByteBuffer

CharBuffer charBuffer = CharBuffer.allocate(10);
        System.out.println("初始limit的值:"+charBuffer.limit());
        System.out.println("初始position的值:"+charBuffer.position());
        System.out.println("初始capcity的值:"+charBuffer.capacity());
        System.out.println("初始mark的值:"+charBuffer.mark());

输出结果:

初始limit的值:10
初始position的值:0
初始capcity的值:10
初始mark的值:  

 

由于ByteBuffer是私有的,只能通过allocate()方法创建,allocate方法是一个静态方法,因此最终创建一个ByteBuffer数组是通过类调用allocate()方法创建,需要传入的参数是容量的大小,底层的具体参数设置如下:

 Buffer(int mark, int pos, int lim, int cap) {       // package-private
//mark初始值为-1,pos为0,lim与cap初始为传入的长度
        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;
        }
    }

JAVA:NIO中的缓冲区(Buffer)详解 -------源码

下面我们在charbuffer数组中存入数据,通过调用它的方法,分析四个属性的变换

 

2.1继承关系

public abstract class ByteBuffer
    extends Buffer
    implements Comparable<ByteBuffer>

ByteBuffer是一个抽象类,继承了Buffer类,实现了Comparable接口,因此ByteBuffer具有Buffer类的方法;即如下方法:

2.1.1调用put()方法存储数据:

 CharBuffer charBuffer = CharBuffer.allocate(10);
        String str="hello";
        charBuffer.put(str.toCharArray());
        System.out.println(charBuffer.length());//剩余容量       5
        System.out.println(charBuffer.limit());//  上界 10  
        System.out.println(charBuffer.capacity());//  最大容量 10
        System.out.println(charBuffer.mark());//  暂时未用为 null
        System.out.println(charBuffer.position());//下一个要写的位置    5

存储"hello"之后四个属性的变化

JAVA:NIO中的缓冲区(Buffer)详解 -------源码

public final ByteBuffer put(byte[] src) {
        return put(src, 0, src.length);
    }

 2.2.2调用filp方法切换模式

   上面通过调用put()方法存储数据,下面我们通过调用filp()方法进入读数据的准备阶段:

 CharBuffer charBuffer = CharBuffer.allocate(10);
        String str="hello";
        charBuffer.put(str.toCharArray());//写如状态
        charBuffer.flip();//切换状态,开始准备读的操作,下面四个属性的变化
        System.out.println(charBuffer.length());//剩余容量       5
        System.out.println(charBuffer.limit());//  上界 5
        System.out.println(charBuffer.capacity());//  最大容量 10
        System.out.println(charBuffer.mark());//  hello
        System.out.println(charBuffer.position());//0

属性的变化情况图示: 

JAVA:NIO中的缓冲区(Buffer)详解 -------源码

源码分析: 

public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

进入读状态,写状态pos的位置为读状态的上界,由图我们也可看出读到下表为5是已经没有数据;读取数据用pos标记,我们需要从头开始读取,因此pos位置重置为0;

2.23get()方法读取缓冲区中的数据

 CharBuffer charBuffer = CharBuffer.allocate(10);
        String str="hello";
        charBuffer.put(str.toCharArray());//写如状态
        charBuffer.flip();//切换状态,开始准备读的操作,下面四个属性的变化

        char c = charBuffer.get();
        System.out.println(c);//h
        System.out.println(charBuffer.length());//剩余容量      4
        System.out.println(charBuffer.limit());//  上界 5
        System.out.println(charBuffer.capacity());//  最大容量 10
        System.out.println(charBuffer.mark());//  ello
        System.out.println(charBuffer.position());//1

JAVA:NIO中的缓冲区(Buffer)详解 -------源码

通过get()读取数据,pos的位置会+1; 

2.2.3清空缓冲区:

public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

清空缓冲区,缓冲区中的参数回到初始状态,但里面存储的数据依然存在,处于“被遗忘”状态;

2.2.4可重复读数据

切换读取模式时使用:

可重复读:

public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }

2.2.5判断是否还有剩余元素:

 public final boolean hasRemaining() {
        return position < limit;
    }

如果pos的值小于lim的值,说明还有数据可读,或者说明还有空间可供存储

2.2.6重置:

通过mark记录当前pos的位置,可以通过reset()恢复到mark的位置

public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }