Java NIO Buffer

Buffer 简介

    Buffer 的作用是用来存储数据 , 可以对数据进行存、取的操作。 Buffer 的空间大小初始化时确定之后是不可变的。 Buffer 中的重要属性 :

  • mark :  标记位置 (初始为 -1)
  • position  :  操作缓冲区的"针针"(说指针是为了方便理解)位置
  • limit : 限制位置 , 如果 limit - position < 要操作(存或取)数据的长度抛出异常 :BufferUnderflowException 。
  • capacity  : Buffer 总容量 byte 大小

    这4个属性永远满足关系 mark ≤ position ≤ limit ≤ capacity , 存、取操作会改变 position 的值。

    java.nio.Buffer 是所有缓冲区的基类 , 提供了缓冲区的基本操作API  :

public abstract class 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;
        }
    }

    // 获取 Buffer 的容量
    public final int capacity();

    // 获取操作缓冲区指针的位置
    public final int position();

    // 设置一个新的缓冲区指针位置
    public final Buffer position(int newPosition);
    
    // 缓冲区操作的限制位置
    public final int limit();

    // 设置一个新的缓冲区操作的限制位置
    public final Buffer limit(int newLimit);

    // 将当前缓冲区指针的位置设置为标记位置
    public final Buffer mark();

    // 将标记位置设置为缓冲区操作指针位置
    public final Buffer reset();

    // 将缓冲区的各个位置还原为初始时的值 , position = 0 ,limit = capacity ,mark = -1 
    public final Buffer clear();

    // 将当前缓冲区操作指针的位置设置为操作上限 , 操作指针位置设置为 0 , 标记位置设置为 - 1
    // limit = position , position = 0 , mark = -1
    public final Buffer flip();

    // 操作指针位置设置为 0 , 标记位置设置为 -1 , 为了可以对缓冲区进行重新读取
    public final Buffer rewind();

    // 获取操作指针位置和上限位置之间剩余的位置大小
    public final int remaining();
    
    // 检测获取操作指针位置和上限位置之间是否还有剩余位置
    public final boolean hasRemaining();

    // ******************** abstract method ********************

    // 检测该缓冲区是否是只读的
    public abstract boolean isReadOnly();

    // 检测该缓冲区中是否有字节数组 
    // 比如堆缓冲区内部是有字节数组的,因为它初始化分配空间的时候使用的就是字节数组
    // 直接缓冲区内部是没有字节数组的
    public abstract boolean hasArray();

    // 获取创建缓冲区是分配的字节数组 , 对该数组做修改等于直接修改缓冲区中的字节数组
    public abstract Object array();

    // 
    public abstract int arrayOffset();

    // 检测是否是直接缓冲区
    public abstract boolean isDirect();

}

        基础 APIs 测试代码 :

@Test
public void basicsApiTest() throws Exception {
    ByteBuffer byteBuffer = ByteBuffer.allocate(20);
//        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(20);

    byteBuffer.put("hello nio".getBytes());
    System.out.println("Buffer put after -> " + byteBuffer);

    byteBuffer.flip();
    System.out.println("Buffer flip after -> " + byteBuffer);

    byte[] dst = new byte[byteBuffer.limit()];
    byteBuffer.get(dst);
    System.out.println("Buffer get after -> " + byteBuffer);
    System.out.println("read data -> " + new String(dst));

    byteBuffer.rewind();
    System.out.println("Buffer rewind after -> " + byteBuffer);
    byte[] dst1 = new byte[byteBuffer.limit()];
    byteBuffer.get(dst1);
    System.out.println("reread data -> " + new String(dst1));
    System.out.println("Buffer get after -> " + byteBuffer);

    System.out.println("Buffer remaining = " + byteBuffer.remaining());
    System.out.println("Buffer hasRemaining = " + byteBuffer.hasRemaining());

    System.out.println("Buffer clear after -> " + byteBuffer.clear());

    System.out.println("Buffer hasArray -> " + byteBuffer.hasArray());
    if (byteBuffer.hasArray()) {
        System.out.println("Buffer clear after array length -> " + byteBuffer.array().length);
        System.out.println("Buffer clear after array -> " + new String(byteBuffer.array()));
        byte[] array = byteBuffer.array();
        for (int i = 0 ; i < array.length ; i++) {
            array[i] = 0;
        }
        System.out.println("change byte array after -> " + new String(byteBuffer.array()));
        System.out.println("Buffer arrayOffset -> " + byteBuffer.arrayOffset());
    }
}

Buffer 的创建  

public abstract class ByteBuffer
    extends Buffer
    implements Comparable<ByteBuffer> {

        // 创建一个堆内存缓冲区  HeapByteBuffer
        public static ByteBuffer allocate(int capacity);

        // 创建一个直接内存缓冲区  DirectByteBuffer
        public static ByteBuffer allocateDirect(int capacity);

        // 用指定的字节数组创建一个堆内存缓冲区
        public static ByteBuffer wrap(byte[] array);

        // 用指定的字节数组创建一个堆内存缓冲区
        public static ByteBuffer wrap(byte[] array,int offset, int length);

        // 创建一个新的字节缓冲区,其内容是此缓冲区内容的共享子序列.
        // 新缓冲区写入的数据不会覆盖原有 buffer 写入的数据,会追加在后面。原有的缓冲区修改后会覆盖新的缓冲区内容
        
        // 如果是堆缓冲区,新的缓冲区与原有缓冲区共享同一个字节数组
        // pos = 0 , lim = byteBuffer.remaining() , cap = byteBuffer.remaining() , off = this.position() + offset

        // 如果是直接缓冲区,新的直接缓冲区与原有缓冲区共享同一内存地址空间
        // pos = this.position() , lim = this.limit() , rem = (pos <= lim ? lim - pos : 0) , off = (pos << 0)
        public abstract ByteBuffer slice();


        // 创建一个新的字节缓冲区,共享原有缓冲区的内容 , 任何一个缓冲区对数据做了变更,会体现在另一个缓冲区上。
        // mark = 原有缓冲区 mark , position = 原有缓冲区 position , limit = 原有缓冲区 limit , 
        // capacity = 原有缓冲区 capacity
        public abstract ByteBuffer duplicate();
}

        缓冲区内存最大分配为 1G , int 类型的最大值。缓冲区一旦创建后容量就是固定的了,不能改变。

        堆内存缓冲区内存在 JVM Heap (堆) 内存中 , 是通过直接创建 byte[] 方式分配的内存 , 创建缓冲区的时候如果超过了 JVM 的内存限制会抛出 OutOfMemoryError 错误。

        直接缓冲区内存空间是直接通过 JNI 直接分配的内存空间 , 创建缓冲区时分配内存空间的大小只受机器内存大小的限制 , 不受 JVM 限制 。这部分内存空间是没法被 JVM 直接回收的。在分配了内存空间后会把内存空间地址返回给 ByteBuffer 。(关于堆外内存回收的问题不讨论 , 有兴趣可以看 ImportNew 堆外内存之 DirectByteBuffer 详解)。

    没有创建缓冲区之前机器内存占用情况:

                                 

     capacity = 1000000000 , 创建堆内存缓冲区之后 :

扫描二维码关注公众号,回复: 1154907 查看本文章

                                 

        capacity = 1000000000 ,创建堆内存缓冲区之后 JVM 内存占用 :

                    

    capacity = 1800000000 , 创建直接缓冲区之后的机器内存占用 :

                            

    capacity = 1800000000 , 创建直接缓冲区之后的 JVM 内存占用 :

                    

 

创建视图缓冲区

        创建视图来将 byte 型缓冲区字节数据映射为其他原始数据类型缓冲区。 新缓冲区的内容将从原有缓冲区的当前位置 (position) 开始。任意一个缓冲区对内容的更改将在另一个缓冲区缓冲区中可见。两个缓冲区的 mark , position , limit , capacity 是各自独立的。新的缓冲区 mark = -1 , position = 0 , limit = 原有缓冲区剩余可操作的字节数 >> 1 , capacity = 原有缓冲区剩余可操作的字节数 >> 1 。

        创建视图缓冲区 APIs :

public abstract class ByteBuffer
    extends Buffer
    implements Comparable<ByteBuffer> {

        // 创建视图来将 byte 型缓冲区字节数据映射为其他原始数据类型缓冲区。
        // 新缓冲区的内容将从原有缓冲区的当前位置 (position) 开始。
        // 任意一个缓冲区对内容的更改将在另一个缓冲区缓冲区中可见。
        // 两个缓冲区的 mark , position , limit , capacity 是各自独立的
        // 新的缓冲区 mark = -1 , position = 0 , limit = 原有缓冲区剩余可操作的字节数 >> 1 , capacity = 原有缓冲区剩余可操作的字节数 >> 1 

        public abstract CharBuffer asCharBuffer();
        public abstract DoubleBuffer asDoubleBuffer();
        public abstract FloatBuffer asFloatBuffer();
        public abstract IntBuffer asIntBuffer();
        public abstract LongBuffer asLongBuffer();
        public abstract ByteBuffer asReadOnlyBuffer();
        public abstract ShortBuffer asShortBuffer();
}

          ByteBuffer 数据元素视图 APIs :

public abstract class ByteBuffer
    extends Buffer
    implements Comparable<ByteBuffer> {
        
        // 读取 1 字节 , position 向右移动 1 位
        public abstract byte get();

        // 读取 2 字节 ,转换成 char 类型 , position 向右移动 2 位
        public abstract char getChar();

        // 读取 2 字节 ,转换成 short 类型 , position 向右移动 2 位
        public abstract short getShort();

        // 读取 4 字节 ,转换成 int 类型, position 向右移动 4 位
        public abstract int getInt();

        // 读取 8 字节 ,转换成 long 类型 , position 向右移动 8 位
        public abstract long getLong();

        // 读取 4 字节 ,转换成 float 类型 , position 向右移动 4 位
        public abstract float getFloat();

        // 读取 8 字节 , 转换成 double 类型, position 向右移动 8 位
        public abstract double getDouble();

        // 相对应的 put 方法也是放入对应数据类型的字节数  , position 向右移动对应的数据类型占用的字节位数
        // put ...

}

Buffer 中数据的存、取

        数据存取的 APIs :

public abstract class ByteBuffer
    extends Buffer
    implements Comparable<ByteBuffer> {
        
        public abstract ByteBuffer put(byte b);

        public final ByteBuffer put(byte[] src);

        public ByteBuffer put(byte[] src, int offset, int length);

        public ByteBuffer put(ByteBuffer src);

        public abstract ByteBuffer put(int index, byte b);

        public abstract byte get();

        public ByteBuffer get(byte[] dst);

        public ByteBuffer get(byte[] dst, int offset, int length);

        public abstract byte get(int index);
}

        数据存储过程简图 :         

               

        在数据的存储和获取操作之间要调整, position , limit 的值 , 否则无法正常对数据进行存、取会抛出异常。对 Buffer 的每次操作都是从左边开始 , 存储和获取都是从左边开始。写入时会随着写入数据大小更新 position 的位置 , 写入完成后如果要从 Buffer 中获取数据,limit 的值应该为当前 position 的值 , 而 position 的值应该为 0 , 这样再读取的时候才能读取到有效的数据。这个过程通过 Buffer 的 flip() 函数实现。

Buffer 释放、标记、压缩、比较

       缓冲区的释放是将缓冲区的 position = 0 , limit = capacity , mark = -1 , 不会清除缓冲区中的数据。

       缓冲区标记是调用 mark() 将 mark  = position  , 通过 reset() 将 position = mark 。 flip() , rewind() , clear() , 这些函数会丢弃 mark (将 mark 设置为 -1) 。 position(newPosition)  , limit(newLimit)  函数有可能丢弃 mark , 当 mark 大于传入的参数时。 

        equals 比较两个缓冲区是否相等, 两个缓冲区被认为是相等的条件是 : 

            1. 两个对象类型相同。

            2. 两个对象剩余同样数量的元素 , buffer1.remaining() == buffer2.remaining() 。

            3. 每个缓冲区中被 get() 获取的元素必须相等。        

        compareTo 比较缓冲区大小 , 比较是针对每个缓冲区内剩余数据进行的,直到不相等的元素被发现或者到达缓冲区的上界。如果一个缓冲区在不相等元素发现前已经被耗尽,较短的缓冲区被认为是小于较长的缓冲区。

字节顺序

    PS : 目前字节被广泛认为是 8 比特。这并非一直是实情,在过去每个字节可以是 3 到 12 之间任何个数或者更多个的比特位,最常见的是 6 到 9 位。 8 位字节来自于市场的力量和 实践的结合。它之所以实用是因为 8 位足以表达可用的字符集 , 8 是 2 的 3 次乘方(这简化了硬件的设计) , 8 位恰好容纳两个16进制数字,而且 8 的倍数提供了足够的组合来存储有效数值。市场力量是 IBM ,在 1960 年首先推出的 IBM 360 大型机实用的就是 8 位字节。

    非字节类型的基本类型,除了布尔类型都是由组合在一起的几个字节组成。每个基本数据类型都是以连续的字节序列的形势存储在内存中。字节存储的顺序有可能是"大端字节顺序" 或是 "小端字节顺序" 。

    多字节数值被存储在内存中的方式一般称为 endian-ness (字节顺序)。如果数字数值的最高字节 —— big end (大端) ,位于低位地址 , 那么系统就是大端字节顺序。如果最低字节最先保存在内存中,就是小端字节顺序。

    字节顺序问题很少由软件设计者决定,通常取决于硬件设计。字节顺序问题甚至胜过了CPU硬件设计。当 Internet 的设计者为互联各种类型的计算机而设计网际协议(IP)时,他们意识到了再具有不同字节顺序的系统间传递数据的问题。因此,IP协议规定了使用大端的网络字节顺序概念。所有在IP分组报文的协议部分中使用的多字节数值必须现在本地主机字节顺序和通用的网络字节之间进行转换。

    ByteOrder 定义了决定从缓冲区中存储或检索多字节数值时使用哪种字节顺序 , 缓冲区默认使用的是大端字节顺序。

public final class ByteOrder {

    // 大端字节顺序
    public static final ByteOrder BIG_ENDIAN = new ByteOrder("BIG_ENDIAN");
    
    // 小端字节顺序
    public static final ByteOrder LITTLE_ENDIAN = new ByteOrder("LITTLE_ENDIAN");
    
    // 获取操作系统使用的字节顺序
    public static ByteOrder nativeOrder();
}

猜你喜欢

转载自my.oschina.net/j4love/blog/1821305
今日推荐