美文网首页
JavaNIO-缓冲区01 Buffer

JavaNIO-缓冲区01 Buffer

作者: 贪睡的企鹅 | 来源:发表于2019-07-12 14:54 被阅读0次

1 简介

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。在JVM中将内存分为堆内内存,堆外内存,所谓堆外即不受gc控制,有的时候也被称为缓冲区,和直接缓冲区,为了简便之后都按堆内内存,堆外内存来说。

缓冲区用于和NIO通道进行交互。数据是从通道读入缓冲区,从缓冲区写入到通道中的。

image

2 类结构

Buffer类是一个抽象类,它具有7 个直接子类,分别是ByteBuffer 、CharBuffer 、DoubleBuffer 、FloatBuffer 、IntBuffer 、LongBuffer 、
ShortBuffer,它们同样是抽象类,要实例化一个ByteBuffer需要实例其子类HeapByteBuffer或DirectByteBuffer。其中HeapByteBuffer表示堆内内存,DirectByteBuffer表示堆外内存

image

3 内存管理

Linux中内存被分一块一块的页,而在Java常用管理数据的方式就是数组,数组是一组连续的内存空间,来存储一组具有相同类型的数据,可以通过下标访问数组元素。但由于在Java语言中对数组自身进行操作的API非常少,如果对数组中的数据进行高级处理,需要程序员自己写代码进行实现,处理的方式是比较原始的。而Buffer提供了一种更高效的管理内存数据的方式。它和数组意义可以管理各种类型数据。

工作原理

作为一块内存,一定会存在读写,当我们读取时Buffer需要切换读模式,当我们写入时Buffer需要切换写模式,其内存通过4个指针来管理内存,当我们申请一块内存缓冲区必须指定其容量,切换写模式每次往缓冲区写入对应类型数据时,其内部会存在一个指针position会伴随写入向后移动,用来表示当前写入的位置。当然缓存区数据容量是有限的当position移动到capacity时在往缓冲区写入数据会抛出数组越界。切换读模式每次从缓冲区读取数据对应类型数据时,其内部会存在一个指针position会伴随写入向后移动,用来表示当前读取的位置,当当position移动到capacity时在往缓冲区读取数据会发现读取失败。有时我们需要对缓冲区中读写做限制,这时可以设置limit,这样读取写入的数据不能操作limit指针指向的位置。有时候我们需要对某个位置做标记,方便在某个时候返回标记,这时我们可以设置mark

image

Buffer源码定义

public abstract class Buffer {

    ...省略代码

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

    long address;

    Buffer(int mark, int pos, int lim, int cap) {       // package-private
        if (cap < 0)
            throw new IllegalArgumentException("Negative capacity: " + cap);
        /** 设置capacity **/    
        this.capacity = cap;
        limit(lim);
        position(pos);
        if (mark >= 0) {
            if (mark > pos)
                throw new IllegalArgumentException("mark > position: ("
                                                   + mark + " > " + pos + ")");
            this.mark = mark;
        }
    }
    /** 设置limit **/
    public final Buffer limit(int newLimit) {
        if ((newLimit > capacity) || (newLimit < 0))
            throw new IllegalArgumentException();
        limit = newLimit;
        if (position > limit) position = limit;
        if (mark > limit) mark = -1;
        return this;
    }
    
    /** 设置position **/
    public final Buffer position(int newPosition) {
        if ((newPosition > limit) || (newPosition < 0))
            throw new IllegalArgumentException();
        position = newPosition;
        if (mark > position) mark = -1;
        return this;
    }
    

从上面我们得出如下结论 0<=mark<=position<=imit<=capacity

简单案例

@Test
public void testBuffer() {
    //申请一块内存
    ByteBuffer allocate = ByteBuffer.allocate(10);
    System.out.println(allocate);
    //写入数据
    allocate.put((byte) 1);
    allocate.put((byte) 2);
    System.out.println(allocate);
    //切换读模式
    allocate.flip();
    //读取数据
    System.out.println(allocate);
    System.out.println(allocate.get());
    System.out.println(allocate.get());
}

java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
java.nio.HeapByteBuffer[pos=2 lim=10 cap=10]
java.nio.HeapByteBuffer[pos=0 lim=2 cap=10]
1
2    

4 Buffer API 操作

image
4.1 申请一块缓冲区

Buffer.java 类是抽象类, 并不能直接实例化,
而其子类: ByteBuffer 、CharBuffer 、DoubleBuffi町、FloatBuff1町、
lntBuffer 、LongBuffer 和ShortBuffer 也是抽象类,也无法实例化,他们核心作用是为子类提供工厂构造函数和通用逻辑实现这里我们已ByteBuffer为例子

使用allocate申请一块内存

/** 申请一块堆内内存Buffer **/
public static ByteBuffer allocate(int capacity) {
    if (capacity < 0)
        throw new IllegalArgumentException();
    return new HeapByteBuffer(capacity, capacity);
}
 
/** 申请一块堆外内存Buffer **/    
public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
}

使用数组实现,实例化Bufferr

/** 使用数组实现,实例化Buffer 
*  offset 表示数组管理起始位置
*  length 表示管理从起始位置开始数据长度
**/    
public static ByteBuffer wrap(byte[] array,
                                    int offset, int length)
{
    try {
        return new HeapByteBuffer(array, offset, length);
    } catch (IllegalArgumentException x) {
        throw new IndexOutOfBoundsException();
    }
}
 
/** 使用数组实现,实例化Buffer **/      
public static ByteBuffer wrap(byte[] array) {
    return wrap(array, 0, array.length);
}    

这里之所以说使用数组实现,实例化Bufferr,是因为ByteBuffer 、CharBuffer 、DoubleBuffi町、FloatBuff1町、
lntBuffer 、LongBuffer 和ShortBuffer都是通过数组来管理内存,我们下面以ByteBuffer为例看代码。

public abstract class ByteBuffer
    extends Buffer
    implements Comparable<ByteBuffer>
{
    /*** 内存使用数组存储数据 **/
    final byte[] hb;
    /**  数组首元素位置 **/
    final int offset;
    /**  是否只读 **/
    boolean isReadOnly;             

    /** 
    *  mark 标记位置
       pos  position标记位置
       lim  limit标记位置
       cap  capacity标记位置
    **/
    ByteBuffer(int mark, int pos, int lim, int cap,   // package-private
                 byte[] hb, int offset)
    {
        super(mark, pos, lim, cap);
        this.hb = hb;
        this.offset = offset;
    }

    
    ByteBuffer(int mark, int pos, int lim, int cap) { // package-private
        this(mark, pos, lim, cap, null, 0);
    }

简单案例

  • 使用数组为参数构造Buffer时 缓冲区中的capacity=arry.length,limit=arry.length
  • 当使用wrap(byte[] array,int offset, int length) position=offset
  • 当使用wrap(byte[] array) position=0
   @Test
    public void testBuffer_wrap() {

        byte[] byteArray = new byte[]{1, 2, 3};
        short[] shortArray = new short[]{1, 2, 3, 4};
        int[] intArray = new int[]{1, 2, 3, 4, 5};
        long[] longArray = new long[]{1, 2, 3, 4, 5, 6};
        float[] floatArray = new float[]{1, 2, 3, 4, 5, 6, 7};
        double[] doubleArray = new double[]{1, 2, 3, 4, 5, 6, 7, 8};
        char[] charArray = new char[]{'a', 'b', 'c', 'd'};

        /** *Buffer.wrap提供了Heap*Buffer的工厂方法  **/
        ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
        ByteBuffer byteBuffer2 = ByteBuffer.wrap(byteArray,1,2);
        ShortBuffer shortBuffer = ShortBuffer.wrap(shortArray);
        IntBuffer intBuffer = IntBuffer.wrap(intArray);
        LongBuffer longBuffer = LongBuffer.wrap(longArray);
        FloatBuffer floatBuffer = FloatBuffer.wrap(floatArray);
        DoubleBuffer doubleBuffer = DoubleBuffer.wrap(doubleArray);
        CharBuffer charBuffer = CharBuffer.wrap(charArray);

        System.out.println("byteBuffer=" + byteBuffer.getClass().getName());
        System.out.println("shortBuffer=" + shortBuffer.getClass().getName());
        System.out.println("intBuffer=" + intBuffer.getClass().getName());
        System.out.println("longBuffer=" + longBuffer.getClass().getName());
        System.out.println("floatBuffer=" + floatBuffer.getClass().getName());
        System.out.println("doubleBuffer=" + doubleBuffer.getClass().getName());
        System.out.println("charBuffer=" + charBuffer.getClass().getName());

        System.out.println();

        System.out.println("byteBuffer.=" + byteBuffer);
        System.out.println("byteBuffer2.=" + byteBuffer2);
        System.out.println("shortBuffer.=" + shortBuffer);
        System.out.println("intBuffer.=" + intBuffer);
        System.out.println("longBuffer.=" + longBuffer);
        System.out.println("floatBuffer.=" + floatBuffer);
        System.out.println("doubleBuffer.=" + doubleBuffer);
        System.out.println("charBuffer.=" + charBuffer);
    }

byteBuffer=java.nio.HeapByteBuffer
shortBuffer=java.nio.HeapShortBuffer
intBuffer=java.nio.HeapIntBuffer
longBuffer=java.nio.HeapLongBuffer
floatBuffer=java.nio.HeapFloatBuffer
doubleBuffer=java.nio.HeapDoubleBuffer
charBuffer=java.nio.HeapCharBuffer

byteBuffer.=java.nio.HeapByteBuffer[pos=0 lim=3 cap=3]
byteBuffer2.=java.nio.HeapByteBuffer[pos=1 lim=3 cap=3]
shortBuffer.=java.nio.HeapShortBuffer[pos=0 lim=4 cap=4]
intBuffer.=java.nio.HeapIntBuffer[pos=0 lim=5 cap=5]
longBuffer.=java.nio.HeapLongBuffer[pos=0 lim=6 cap=6]
floatBuffer.=java.nio.HeapFloatBuffer[pos=0 lim=7 cap=7]
doubleBuffer.=java.nio.HeapDoubleBuffer[pos=0 lim=8 cap=8]
charBuffer.=abcd   
4.2 限制获取与设置

设置limit逻辑

  • 1 newLimit不能大于capacity,newLimit不能小于0
  • 2 如果position小于limit,将position移动到limit处
  • 3 如果mark大于limit,mark被取消
/** 返回limit **/
public final int limit() {
        return limit;
}

/** 设置limit **/
public final Buffer limit(int newLimit) {
    /** newLimit不能大于capacity,newLimit不能小于0 **/
    if ((newLimit > capacity) || (newLimit < 0))
        throw new IllegalArgumentException();
    limit = newLimit;
    /** 如果position小于limit,将position移动到limit处 **/
    if (position > limit) position = limit;
    /** 如果mark大于limit,mark被取消 **/
    if (mark > limit) mark = -1;
    return this;
}

案例

    @Test
    public void testBuffer_limit() {
        ByteBuffer buffer = ByteBuffer.allocate(6);
        System.out.println(buffer);
        buffer.limit(3);
        System.out.println();
        System.out.println(buffer);
        buffer.put((byte) 1);
        buffer.put((byte) 2);
        buffer.put((byte) 3);
        buffer.put((byte) 4);  //BufferOverflowException


        byte[] byteArray = new byte[]{1,2,3,4,5,6};
        ByteBuffer buffer2 = ByteBuffer.wrap(byteArray);
        System.out.println(buffer2);
        buffer2.limit(3);
        System.out.println();
        System.out.println(buffer2);
        System.out.println(buffer2.get());
        System.out.println(buffer2.get());
        System.out.println(buffer2.get());
        System.out.println(buffer2.get()); //BufferUnderflowException
    }
4.3 位置获取与设置

设置position逻辑

  • 1 newPosition不能大于limit,newPosition不能小于0
  • 3 如果mark大于position,mark被取消
/** 返回position **/
public final int position() {
        return position;
}


/** 返回position **/
public final Buffer position(int newPosition) {
    if ((newPosition > limit) || (newPosition < 0))
        throw new IllegalArgumentException();
    position = newPosition;
    if (mark > position) mark = -1;
    return this;
}

案例

 @Test
    public void testBuffer_position() {
        /** 写入 **/
        ByteBuffer buffer = ByteBuffer.allocate(6);
        buffer.put((byte) 1);
        buffer.put((byte) 2);
        System.out.println(buffer);
        buffer.position(0);
        System.out.println(buffer);
        buffer.put((byte) 3);
        buffer.put((byte) 4);  //BufferOverflowException
        for (int i = 0; i < buffer.array().length; i++) {
            System.out.println(buffer.array()[i] + " ");
        }
        System.out.println("-------------------------");
        /** 读取 **/
        byte[] byteArray = new byte[]{1,2,3,4,5,6};
        ByteBuffer buffer2 = ByteBuffer.wrap(byteArray);
        System.out.println(buffer2.get());
        System.out.println(buffer2.get());
        System.out.println(buffer2);
        buffer.position(0);
        System.out.println(buffer2);
        System.out.println(buffer2.get());
        System.out.println(buffer2.get());
    }

java.nio.HeapByteBuffer[pos=2 lim=6 cap=6]
java.nio.HeapByteBuffer[pos=0 lim=6 cap=6]
3 
4 
0 
0 
0 
0 
-------------------------
1
2
java.nio.HeapByteBuffer[pos=2 lim=6 cap=6]
java.nio.HeapByteBuffer[pos=2 lim=6 cap=6]
3
4
    
4.4 剩余空间大小获取
public final int remaining() {
        return limit - position;
    }


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

案例

    @Test
    public void testBuffer_remaining() {
        /** 写入 **/
        ByteBuffer buffer = ByteBuffer.allocate(6);
        buffer.put((byte) 1);
        System.out.println(buffer);
        System.out.println(buffer.remaining());
        buffer.put((byte) 2);
        System.out.println(buffer);
        System.out.println(buffer.remaining());

        System.out.println("-------------------------");
        /** 读取 **/
        byte[] byteArray = new byte[]{1,2,3,4,5,6};
        ByteBuffer buffer2 = ByteBuffer.wrap(byteArray);
        buffer2.get();
        System.out.println(buffer2);
        System.out.println(buffer2.remaining());
        buffer2.get();
        System.out.println(buffer2);
        System.out.println(buffer2.remaining());
    }

java.nio.HeapByteBuffer[pos=1 lim=6 cap=6]
5
java.nio.HeapByteBuffer[pos=2 lim=6 cap=6]
4
-------------------------
java.nio.HeapByteBuffer[pos=1 lim=6 cap=6]
5
java.nio.HeapByteBuffer[pos=2 lim=6 cap=6]
4    
4.5 处理标记
/**
 * 在此位置设置此缓冲区的标记。
 */
public final Buffer mark() {
    mark = position;
    return this;
}

/**
 * 将position位置重置为先前标记的位置。
 */
public final Buffer reset() {
    int m = mark;
    if (m < 0)
        throw new InvalidMarkException();
    position = m;
    return this;
}

案例

 @Test
    public void testBuffer_mark() {
        /** 写入 **/
        ByteBuffer buffer = ByteBuffer.allocate(6);
        buffer.mark();
        buffer.put((byte) 1);
        buffer.put((byte) 2);
        System.out.println(buffer);
        buffer.reset();
        buffer.put((byte) 3);
        buffer.put((byte) 4);
        for (int i = 0; i < buffer.array().length; i++) {
            System.out.println(buffer.array()[i] + " ");
        }

        System.out.println("-------------------------");
        /** 读取 **/
        byte[] byteArray = new byte[]{1,2,3,4,5,6};
        ByteBuffer buffer2 = ByteBuffer.wrap(byteArray);
        buffer2.mark();
        System.out.println(buffer2.get());
        System.out.println(buffer2.get());
        System.out.println(buffer2);
        buffer2.reset();
        System.out.println(buffer2);
        System.out.println(buffer2.get());
        System.out.println(buffer2.get());
    }

java.nio.HeapByteBuffer[pos=2 lim=6 cap=6]
3 
4 
0 
0 
0 
0 
-------------------------
1
2
java.nio.HeapByteBuffer[pos=2 lim=6 cap=6]
java.nio.HeapByteBuffer[pos=0 lim=6 cap=6]
1
2    
4.6 判断只读

判断缓冲区是否只读Buffer定义为模板方法,我们前面知道ByteBuffer 、CharBuffer 、DoubleBuffi町、FloatBuff1町、
lntBuffer 、LongBuffer 和ShortBuffer内部是通过管理数组来管理内存,因而会覆盖此方法返回false

//Buffer判断只读
public abstract boolean isReadOnly();

//ByteBuffer实现
public boolean isReadOnly() {
    return false;
}

案例

@Test
    public void testBuffer_isRead() {
        byte[] byteArray = new byte[]{1, 2, 3};
        short[] shortArray = new short[]{1, 2, 3, 4};
        int[] intArray = new int[]{1, 2, 3, 4, 5};
        long[] longArray = new long[]{1, 2, 3, 4, 5, 6};
        float[] floatArray = new float[]{1, 2, 3, 4, 5, 6, 7};
        double[] doubleArray = new double[]{1, 2, 3, 4, 5, 6, 7, 8};
        char[] charArray = new char[]{'a', 'b', 'c', 'd'};

        /** *Buffer.wrap提供了Heap*Buffer的工厂方法  **/
        ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
        ShortBuffer shortBuffer = ShortBuffer.wrap(shortArray);
        IntBuffer intBuffer = IntBuffer.wrap(intArray);
        LongBuffer longBuffer = LongBuffer.wrap(longArray);
        FloatBuffer floatBuffer = FloatBuffer.wrap(floatArray);
        DoubleBuffer doubleBuffer = DoubleBuffer.wrap(doubleArray);
        CharBuffer charBuffer = CharBuffer.wrap(charArray);

        System.out.println("byteBuffer=" + byteBuffer.isReadOnly());
        System.out.println("byteBuffer=" + shortBuffer.isReadOnly());
        System.out.println("byteBuffer=" + intBuffer.isReadOnly());
        System.out.println("byteBuffer=" + longBuffer.isReadOnly());
        System.out.println("byteBuffer=" + floatBuffer.isReadOnly());
        System.out.println("byteBuffer=" + doubleBuffer.isReadOnly());
        System.out.println("byteBuffer=" + charBuffer.isReadOnly());
    }

byteBuffer=false
byteBuffer=false
byteBuffer=false
byteBuffer=false
byteBuffer=false
byteBuffer=false
byteBuffer=false    

4.7 判断是否是直接缓冲区

/** 判断是否是直接缓冲区,模板方法,子类实现 **/
public abstract boolean isDirect();


//HeapByteBuffer实现
public boolean isDirect() {
    return false;
}
    
//DirectByteBuffer实现
public boolean isDirect() {
    return true;
}    

案例

@Test
    public void testBuffer_isDirect() {
        System.out.println(ByteBuffer.allocate(10).isDirect());
        System.out.println(ByteBuffer.allocateDirect(10).isDirect());
    }

false
true    
4.8 还原缓冲区的状态

将缓冲区还原到初始状态,这里只是改变其中的指针并没有清理数据

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

案例

    @Test
    public void testBuffer_clear() {
        /** 读取 **/
        byte[] byteArray = new byte[]{1,2,3,4,5,6};
        ByteBuffer buffer2 = ByteBuffer.wrap(byteArray);
        buffer2.limit(3);
        System.out.println(buffer2);
        System.out.println(buffer2.get());
        buffer2.clear();
        System.out.println(buffer2);
        System.out.println(buffer2.get());
    }

java.nio.HeapByteBuffer[pos=0 lim=3 cap=6]
1
java.nio.HeapByteBuffer[pos=0 lim=6 cap=6]
1    
4.9 切换读写模式

position 设置为0
limit 设置为position

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

案例

@Test
public void testBuffer() {
    //申请一块内存
    ByteBuffer allocate = ByteBuffer.allocate(10);
    System.out.println(allocate);
    //写入数据
    allocate.put((byte) 1);
    allocate.put((byte) 2);
    System.out.println(allocate);
    //切换读模式
    allocate.flip();
    //读取数据
    System.out.println(allocate);
    System.out.println(allocate.get());
    System.out.println(allocate.get());
}

java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
java.nio.HeapByteBuffer[pos=2 lim=10 cap=10]
java.nio.HeapByteBuffer[pos=0 lim=2 cap=10]
1
2    
4.10 是否数组实现

是否数组实现在Buffer是模板方法,是由子类实现,前面说了ByteBuffer 、CharBuffer 、DoubleBuffi町、FloatBuff1町、
lntBuffer 、LongBuffer 和ShortBuffer,使用warp都是使用数组实现

//是否数组实现
public abstract boolean hasArray();


//ByteBuffer实现
public final boolean hasArray() {
    return (hb != null) && !isReadOnly;
}

案例

@Test
public void testBuffer_hasArray(){
    /** 读取 **/
    byte[] byteArray = new byte[]{1,2,3,4,5,6};
    ByteBuffer buffer2 = ByteBuffer.wrap(byteArray);
    System.out.println(buffer2.hasArray());
}
4.11 重绕缓冲区

和clear不同的是并不修改limit

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

案例

@Test
    public void testBuffer_rewind(){
        /** 读取 **/
        byte[] byteArray = new byte[]{1,2,3,4,5,6};
        ByteBuffer buffer2 = ByteBuffer.wrap(byteArray);
        buffer2.limit(3);
        System.out.println(buffer2);
        System.out.println(buffer2.get());
        buffer2.rewind();
        System.out.println(buffer2);
        System.out.println(buffer2.get());
    }

java.nio.HeapByteBuffer[pos=0 lim=3 cap=6]
1
java.nio.HeapByteBuffer[pos=0 lim=3 cap=6]
1    

相关文章

网友评论

      本文标题:JavaNIO-缓冲区01 Buffer

      本文链接:https://www.haomeiwen.com/subject/yanekctx.html