欢迎访问昆山宝鼎软件有限公司网站! 设为首页 | 网站地图 | XML | RSS订阅 | 宝鼎邮箱 | 后台管理


新闻资讯

MENU

软件开发知识
原文出处: 木杉的博客

Buffer是Java NIO中对付缓冲区的封装。在Java BIO中,所有的读写API,都是直接利用byte数组作为缓冲区的,简朴直接。可是在Java NIO中,缓冲区这一观念变得巨大,大概是对应Java堆中的一块内存,也大概是对该当地内存中的一块内存。而byte数组只能用来指定Java堆中的一块内存,所以Java NIO中设计了一个新的缓冲区抽象,涵盖了差异范例缓冲区,这个抽象就是Buffer。

Buffer

Buffer是Java NIO中对付缓冲区的抽象。是一个用于存储特定根基数据范例的容器。Buffer是特定根基数据范例的线性有限序列。

Java有8中根基范例:byte,short,int,long,float,double,char,boolean,除了boolean范破例,其他的范例都有对应的Buffer详细实现:

Buffer是特定根基数 <a href=昆山软件开拓 据范例的线性有限序列" class="aligncenter size-large wp-image-29474" title="buffers" src="/uploads/allimg/c180811/1533931N9DZ-14294.png" />

Buffer抽象类界说了所有范例的Buffer都有的属性和操纵,属性如下:

  • capacity:缓冲区的容量,在缓冲区成立后就不能改变
  • limit:暗示第一个不能读写的元素位置,limit不会大于capacity
  • position:暗示下一个要读写的元素位置,position不会大于limit
  • mark:用于暂存一个元素位置,和书签一样,用于后续操纵
  • 所有的Buffer操纵都环绕这些属性举办。这些属性满意一个稳定式:0<=mark<=position<=limit<=capacity

    新建的Buffer这些属性的取值为:

  • position=0
  • limit=capacity=用户配置的容量
  • mark=-1
  • 直接看界说较量抽象,可以看一下示意图,下图是一个容量为10的Buffer:

    Buffer是特定根基数 <a href=昆山软件开拓 据范例的线性有限序列" class="aligncenter size-full wp-image-29475" title="buffer-init" src="/uploads/allimg/c180811/1533931Na5U0-264E.png" />

    ByteBuffer的详细实现

    所有Buffer实现中,最重要的实现是ByteBuffer,因为操纵系统中所有的IO操纵都是对字节的操纵。当我们需要从字节缓冲区中读取此外数据范例才需要利用其他详细范例的Buffer实现。

    ByteBuffer也是一个抽象类,详细的实现有HeapByteBuffer和DirectByteBuffer。别离对应Java堆缓冲区与堆外内存缓冲区。Java堆缓冲区本质上就是byte数组,所以实现会较量简朴。而堆外内存涉及到JNI代码实现,较为巨大,本次我们以HeapByteBuffer为例来阐明Buffer的相关操纵,后续专门阐明DirectByteBuffer。

    ByteBuffer的类图如下:

    Buffer是特定根基数 <a href=昆山软件开拓 据范例的线性有限序列" class="aligncenter size-full wp-image-29476" title="bytebuffer-impl" src="/uploads/allimg/c180811/1533931NaX10-36392.png" />

    读写Buffer

    Buffer作为缓冲区,最主要的浸染是用于通报数据。Buffer提供了一系列的读取与写入操纵。因为差异范例的Buffer读写的范例差异,所以详细的要领界说是界说在Buffer实现类中的。与读写相关的API如下:

    byte get()
    byte get(int index)
    ByteBuffer get(byte[] dst, int offset, int length)
    ByteBuffer get(byte[] dst)
    
    ByteBuffer put(byte b)
    ByteBuffer put(int index, byte b)
    ByteBuffer put(ByteBuffer src) 
    ByteBuffer put(byte[] src, int offset, int length)

    Buffer的读写操纵可以凭据两种维度分类:

  • 单个/批量:
  • 单个:一次读写一个字节
  • 批量:一次读写多个字节
  • 相对/绝对:
  • 相对:从Buffer维护的position位置开始读写,读写时position会随之变革
  • 绝对:直接指定读写的位置。指定index的API就是绝对API
  • 接着我们来看看这些函数在HeapByteBuffer中是如何实现的:

    final byte[] hb;    // 作为缓冲区的byte数组              
    final int offset;   // 指定缓冲区的起始位置
    
    public byte get() {
        // get操纵就是直接从数组中获取数据
        return hb[ix(nextGetIndex())];
    }
    
    public byte get(int i) {
        // 从指定位置获取数据,是绝对操纵,只需查抄下标是否正当
        return hb[ix(checkIndex(i))];
    }
    
    // 获取下一个要读取的元素的下标
    // position的界说就是下一个要读写的元素位置,
    // 所以这里是返回position的当前值,然后再对position举办加一操纵
    final int nextGetIndex() {                          // package-private
        if (position >= limit)
            throw new BufferUnderflowException();
        return position++;
    }
    
    // 因为支持偏移量,所以算出来的下标还需要加上偏移量
    protected int ix(int i) {
        return i + offset;
    }

    单字节put与get逻辑一样。看一下批量get是如何实现的:

    public ByteBuffer get(byte[] dst) {
        return get(dst, 0, dst.length);
    }
    
    public ByteBuffer get(byte[] dst, int offset, int length) {
        // 查抄参数是否越界
        checkBounds(offset, length, dst.length);
        // 查抄要获取的长度是否大于Buffer中剩余的数据长度
        if (length > remaining())
            throw new BufferUnderflowException();
        // 挪用System.arraycopy举办数组内容拷贝
        System.arraycopy(hb, ix(position()), dst, offset, length);
        // 更新position
        position(position() + length);
        return this;
    }