ByteBuffer详解

在NIO网络编程中,通道直接从ByteBuffer中读取数据。Buffer类及其子类定义了一个用于处理数据缓冲区的api。Buffer类定义了所有的缓冲区都具有的四个属性来提供关于其所包含的数据元素的信息。

// Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;

下面详细介绍下

属性 描述
Capacity 容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变
Limit 表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的
Position 位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变改值,为下次读写作准备
Mark 标记,调用mark()来设置mark=position,再调用reset()可以让position恢复到标记的位置

缓冲区相关api

public abstract class Buffer {
    //JDK1.4时,引入的api
    public final int capacity( )//返回此缓冲区的容量
    public final int position( )//返回此缓冲区的位置
    public final Buffer position (int newPositio)//设置此缓冲区的位置
    public final int limit( )//返回此缓冲区的限制
    public final Buffer limit (int newLimit)//设置此缓冲区的限制
    public final Buffer mark( )//在此缓冲区的位置设置标记
    public final Buffer reset( )//将此缓冲区的位置重置为以前标记的位置
    public final Buffer clear( )//清除此缓冲区
    public final Buffer flip( )//反转此缓冲区
    public final Buffer rewind( )//重绕此缓冲区
    public final int remaining( )//返回当前位置与限制之间的元素数
    public final boolean hasRemaining( )//告知在当前位置和限制之间是否有元素
    public abstract boolean isReadOnly( );//告知此缓冲区是否为只读缓冲区
 
    //JDK1.6时引入的api
    public abstract boolean hasArray();//告知此缓冲区是否具有可访问的底层实现数组
    public abstract Object array();//返回此缓冲区的底层实现数组
    public abstract int arrayOffset();//返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量
    public abstract boolean isDirect();//告知此缓冲区是否为直接缓冲区
}

Buffer类的7种基本数据类型(ByteBuffer、CharBuffer、IntBuffer、ShortBuffer、LongBuffer、DoubleBuffer、FloatBuffer)的缓冲区实现都是抽象的。且父类(Buffer)也没有提供存取函数。

public abstract class CharBuffer
    extends Buffer
    implements Comparable<CharBuffer>, Appendable, CharSequence, Readable
{}

在子类中提供了静态工厂方法来创建相应类的实例,以及get、put操作来实现缓存区的存取。

public abstract class ByteBuffer {
    //缓冲区创建相关api
    public static ByteBuffer allocateDirect(int capacity)//创建直接缓冲区
    public static ByteBuffer allocate(int capacity)
    public static ByteBuffer wrap(byte[] array)
    public static ByteBuffer wrap(byte[] array,int offset, int length)//构造初始化位置offset和上界length的缓冲区
     //缓存区存取相关API
    public abstract byte get( );//从当前位置position上get,get之后,position会自动+1
    public abstract byte get (int index);//从绝对位置get
    public abstract ByteBuffer put (byte b);//从当前位置上普通,put之后,position会自动+1
    public abstract ByteBuffer put (int index, byte b);//从绝对位置上put
 }

两大种创建缓冲对象的区别在于是否分配空间来存储数据。
allocate方法创建并分配一个私有的空间来储存指定容量大小的数据元素。
wrap方法会创建一个缓冲区对象,但不分配任何空间来存储数据元素。构造器的入参参数数组来存储缓冲区的数据元素。
下面是创建缓冲区对象的demo

//方式1:allocate方式直接分配,内部将隐含的创建一个数组
        ByteBuffer allocate = ByteBuffer.allocate(10);
        //方式2:通过wrap根据一个已有的数组创建
        byte[] bytes=new byte[10];
        ByteBuffer wrap = ByteBuffer.wrap(bytes);
        //方式3:通过wrap根据一个已有的数组指定区间创建
        ByteBuffer wrapoffset = ByteBuffer.wrap(bytes,2,5);

下面详细介绍下经常用到的方法
·flip()

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

其实就是buffer.limit(buffer.position()).position(0);将一个能够继续添加数据元素的填充状态的缓冲区翻转成一个准备读出元素的释放状态。
·remaining()返回从当前位置到上限的元素个数

/**
     * Returns the number of elements between the current position and the
     * limit. </p>
     *
     * @return  The number of elements remaining in this buffer
     */
public final int remaining() {
        return limit - position;
    }

· hasRemaining()返回是否已达到上限

 /**
     * Tells whether there are any elements between the current position and
     * the limit. </p>
     *
     * @return  <tt>true</tt> if, and only if, there is at least one element
     *          remaining in this buffer
     */
    public final boolean hasRemaining() {
        return position < limit;
    }

·compact()将所有未读数据拷贝到Buffer起始处,丢弃已经释放的数据,保留未释放的数据,然后将position设置到最后一个未读元素的正后面。

 public ByteBuffer compact() {
        int pos = position();
        int lim = limit();
        assert (pos <= lim);
        int rem = (pos <= lim ? lim - pos : 0);
        unsafe.copyMemory(ix(pos), ix(0), rem << 0);
        position(rem);
        limit(capacity());
        discardMark();
        return this;
    }

复制缓冲区

最后讲一下缓冲区的复制。缓冲区的复制有分两种:
1、完全复制:调用duplicate()函数或者asReadOnlyBuffer()函数
2、部分复制:调用slice函数
duplicate()函数创建了一个与原始缓冲区相似的新缓冲区。两个缓冲区共享数据元素,拥有同样的容量,但每个缓冲区拥有各自的位置,上界和标记属性。对一个缓冲区内的数据元素所做的改变会反映在另外一个缓冲区上。这一副本缓冲区具有与原始缓冲区同样的数据视图。如果原始的缓冲区为只读,或者为直接缓冲区,新的缓冲区将继承这些属性。

CharBuffer buffer = CharBuffer.allocate (8);

buffer.position (3).limit (6).mark( ).position (5);

CharBuffer dupeBuffer = buffer.duplicate( );

buffer.clear( );

参考文章:http://www.tianshouzhi.com/api/tutorials/netty/315

posted @ 2017-11-26 19:55  kivi2  阅读(6045)  评论(0编辑  收藏  举报