ByteBuffer 字节缓冲区

 

 

     
HeapByteBuffer 在jvm堆上面的一个buffer,底层的本质是一个数组  由于内容维护在jvm里,所以把内容写进buffer里速度会快些;并且,可以更容易回收
DirectByteBuffer 底层的数据其实是维护在操作系统的内存中,而不是jvm里,DirectByteBuffer里维护了一个引用address指向了数据,从而操作数据  跟外设(IO设备)打交道时会快很多,因为外设读取jvm堆里的数据时,不是直接读取的,而是把jvm里的数据读到一个内存块里,再在这个块里读取的,如果使用DirectByteBuffer,则可以省去这一步,实现zero copy
ByteBuffer的属性
byte[] buff  //buff即内部用于缓存的数组。
position //当前读取的位置。
mark //为某一读过的位置做标记,便于某些时候回退到该位置。
capacity //初始化时候的容量。是它所包含的元素的数量,缓冲区的容量不能为负并且不能更改
limit //当写数据到buffer中时,limit一般和capacity相等,当读数据时,limit代表buffer中有效数据的长度。

这些属性总是满足以下条件: 0 <= mark <= position <= limit <= capacity

ByteBuffer的常规方法
ByteBuffer allocate(int capacity) //创建一个指定capacity的ByteBuffer。
ByteBuffer allocateDirect(int capacity) //创建一个direct的ByteBuffer,这样的ByteBuffer在参与IO操作时性能会更好
ByteBuffer wrap(byte [] array)
ByteBuffer wrap(byte [] array, int offset, int length) //把一个byte数组或byte数组的一部分包装成ByteBuffer。
//get put方法不多说
byte get(int index)
ByteBuffer put(byte b)
int getInt()             //从ByteBuffer中读出一个int值。
ByteBuffer putInt(int value)  // 写入一个int值到ByteBuffer中。
ByteBuffer的特殊方法
Buffer clear()    //把position设为0,把limit设为capacity,一般在把数据写入Buffer前调用。
Buffer flip()    //把limit设为当前position,把position设为0,一般在从Buffer读出数据前调用。
Buffer rewind()   //把position设为0,limit不变,一般在把数据重写入Buffer前调用。和 clear() 类似,只是不改动限制
compact()         //将 position 与 limit之间的数据复制到buffer的开始位置,复制后 position = limit -position,limit = capacity, 但如         果position 与limit 之间没有数据的话发,就不会进行复制。
mark() & reset()  //通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。

put
写模式下,往buffer里写一个字节,并把postion移动一位。写模式下,一般limit与capacity相等。
flip
写完数据,需要开始读的时候,将postion复位到0,并将limit设为当前postion。
get
从buffer里读一个字节,并把postion移动一位。上限是limit,即写入数据的最后位置。
clear
将position置为0,并不清除buffer内容。
mark & reset
mark相关的方法主要是mark()(标记)和reset()(回到标记).

 

/**
 * 1.txt => VipSoftAAA
 *
 * @throws Exception
 */
@Test
public void readFile() throws Exception {
    FileChannel channel = FileChannel.open(Paths.get("D:\\temp\\1.txt"), StandardOpenOption.READ);
    ByteBuffer buf = ByteBuffer.allocate(7);
    while (channel.read(buf) != -1) {
        buf.flip();
        String ss = new String(buf.array());
        System.out.println(ss); // 这种方式的读取,不会产生 position 的变化

        int idx = 0;
        while (buf.hasRemaining()) {
            /**
             * get() 后 position 会往后+1
             * 如果执行完成后,没有 clear(),则新的文件内容不会被读出来。因为 position 已经到了 capacity 值了,没地方放新内容了
             */
            System.out.print((char) buf.get());
            if (idx == 2) {
                /**
                 * 将 position 与 limit之间的数据复制到buffer的开始位置
                 * 复制后,position = (pos <= lim ? lim - pos : 0) 接着往后读
                 * 所以第二次取出AAA时,重置后,position =0 ,这时候会就把buf里面的数据重新再打印一遍变成 AAAAAAtoft
                 */
                buf.compact();

                String compactStr = new String(buf.array());
                System.out.println("\r\nCompact => " + compactStr); // compact后一次性读出来看变化,发现Soft被复制到了前面
            }
            idx++;
        }
        //第二次读出4个字节,但输出还是AAASoft,说明 clear 只是把 position 设为0, 并没有清除数据
        buf.clear();
        System.out.println();
    }
    channel.close();
    System.out.println();
}
//输出结果:
//VipSoft         //buf.array() 一次性打印全部,无偏移
//Vipoft          //compact=7-3=4,所以跳过第三位的S,从第4位开始取值
//AAAtoft         //一次性打印全部,因为 compact 将后position~limit的数据复制到了开始位置,所以AAA后面多了个 t 
//AAAAAAtoft      //第二次 pos>lim 所以 pos=0 把第一次的内容又打了一遍

 

拿第一个例子来分析,这三个属性的作用,capacity(容量)、limit(限制)和position(位置)

1.分配内存大小为10的缓存区。索引10的空间是我虚设出来,实际不存在,为了能明显表示capacity。IntBuffer的容量为10,所以capacity为10,在这里指向索引为10的空间。Buffer初始化的时候,limit和capacity指向同一索引。position指向0。

 

2.往Buffer里加一个数据。position位置移动,capacity不变,limit不变。

 

3.Buffer读完之后,往bufer里写了5个数据,position指向索引为5的第6个数据,capacity不变,limit不变。

 

4.执行flip()。这时候对照着,之前flip源码去看。把position的值赋给limit,所以limit=5,然后position=0。capacity不变。结果就是:

 

5.Buffer开始往外写数据。每写一个,position就下移一个位置,一直移到limit的位置,结束。

 

 

 上图的顺序就是代码中的IntBuffer从初始化,到读数据,再写数据三个状态下,capacity,position,limit三个属性的变化和关系。

大家可以发现:
1. 0 <= position <= limit <= capacity
2. capacity始终不变 

@Test
public void nioTest() {
    // 分配内存大小为10的缓存区
    IntBuffer buffer = IntBuffer.allocate(10);
    System.out.println("capacity:" + buffer.capacity());
    for (int i = 0; i < 5; ++i) {
        System.out.println("put data position:" + buffer.position());
        buffer.put(i+1);
    }
    System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    System.out.println(">>>>>>>> position:" + buffer.position());
    System.out.println(">>>>>>>> limit:" + buffer.limit());
    System.out.println(">>>>>>>> capacity:" + buffer.capacity());
    System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    System.out.println(">>>>> flip() 使缓冲区为一系列新的通道写入或相对获取 操作做好准备<<<<<");
    buffer.flip();
    System.out.println("after flip limit:" + buffer.limit());
    System.out.println("enter while loop");
    while (buffer.hasRemaining()) {
        System.out.println(">>>>>>>> position:" + buffer.position());
        System.out.println(">>>>>>>> limit:" + buffer.limit());
        System.out.println(">>>>>>>> capacity:" + buffer.capacity());
        System.out.println(">>>>>>>> 元素:" + buffer.get());
    }
    System.out.println(">>>>> rewind() 将位置 position = 0 也就是相当于选取当前缓冲区总的全部有效数据 和clear()类似,只是不改动限制 <<<<<");
    buffer.rewind();
    System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    System.out.println(">>>>>>>> position:" + buffer.position());
    System.out.println(">>>>>>>> limit:" + buffer.limit());
    System.out.println(">>>>>>>> capacity:" + buffer.capacity());
    System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    System.out.println(">>>>> clear() 用来初始化缓存空间,例如读取文件时将文件内容置入缓存时要先执行此方法 <<<<<");
    buffer.clear();
    System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
    System.out.println(">>>>>>>> position:" + buffer.position());
    System.out.println(">>>>>>>> limit:" + buffer.limit());
    System.out.println(">>>>>>>> capacity:" + buffer.capacity());
    System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
}

 

 

 

 

posted @ 2022-07-07 12:42  VipSoft  阅读(210)  评论(0编辑  收藏  举报