zoukankan      html  css  js  c++  java
  • Buffer

    前言

    上一篇文章中Java NIO概括性的介绍了Java Nio以及各个核心组件。这篇继续Java Nio的话题,着重了解下Nio中Buffer的原理、Buffer的行为、Buffer种类。

    • Buffer原理
    • Buffer行为
    • Buffer分类

    Buffer原理

    上篇中简书了Buffer是数据容器,可以重复读写。那么Buffer是如何存储数据的呢?Buffer的读写操作是如何标记读写的位置呢?带着这些问题,我们一起先来看下Buffer的存储,以及属性标记。

    线性数组是一门最基础的数据结构,对很多数据结构提供的基本的存储支持。Buffer中使用了线性数组作为其存储实现,即一块连续的存储空间。
    Buffer中有四个非常重要的属性位:

    • capacity: 表示Buffer的容量;
    • limit: Buffer的读写限制,表示最大能读写到limit的位置;
    • position: 记录下一个读写的位置;
    • mark::记录打标记的位置;

    还是一如既往,先直观地图解Buffer

    如上图,是一个容量为n的Buffer的初始化状态。创建Buffer时:

    1. 首先分配容量为n的数组,capacity为n,此时limit设置为n,表示最大的可写限制为n;
    2. position初始为0,表示即将写入的位置是槽位0
    3. mark初始化为-1,初始化的Buffer无标记位

    Buffer中有几个非常重要的操作会导致Buffer的属性位发生变化

    • flip: 翻转Buffer
    • clear:清理Buffer
    • mark:标记Buffer

    属性位的变化表示Buffer的状态切换,每种状态的Buffer都对应Buffer可操作的相应行为

    上图展示了Buffer随着flip、clear、mark操作的属性变更和状态转移:初始化状态 -> 一次put后 -> flip后 -> 一个get后

    两次put后,position移动两位,即position=2,表示下一个写入的位置,limit等于capacity表示Buffer最大支持的写入限制。
    flip翻转主要是在Buffer的读写之间进行切换,flip后,将limit设置为position的大小,position归零,从0位开始读,读到limit为止。
    一次get后,则将position移动一位,表示下一个即将读的位置。

    从上可以看出,相应的操作导致属性位变化,Buffer的可读可写状态之间切换。Buffer基于此实现读写操作。

    Buffer的mark用来给Buffer当前的postion位置打标记,即mark记录当前postion位置,当Buffer发生reset重置时,将postion设置为之前的mark位置,主要用来实现重复读写,持续读写。(复位代表着可持续操作!!)

    Buffer行为

    上一节中详细说明了Buffer的存储,读写原理。这一节中继续了解Buffer的常用行为。这里以字节缓冲区ByteBuffer(存储字节数据)的作为例子进行说明。
    ByteBuffer的常用行为无非创建、读写,但是ByteBuffer对于读写需要遵循上述的flip,clear等操作。

    1)ByteBuffer创建主要通过静态allocate方法实现(工厂模式)

    ByteBuffer buffer = ByteBffer.allocate(16);
    

    这里创建大小为16个字节容量的Buffer。

    2)读操作主要通过get和get的变体方法实现,字节get方法主要有四种形式:

    • get():获取当前position槽位的值
    • get(byte[] b):从当前position获取内容至数组b中,直到填满数组。如果buffer数据不足,会抛出BufferUnderflowException
    • get(byte b[], int offset, int length) 从当前position获取内容至数组b中,从数组的偏移offset位开始填充,共获取length个
    • get(int index) 获取指定槽位的byte,position不发生偏移

    3)写操作:主要通过put和put的变体方法实现,字节put方法主要有五种形式:

    • put(byte b):向当前position槽位写入一个字节
    • put(byte[] bs):从当前position槽位开始写入字节数组,如果buffer容量不足,则抛出:BufferUnderflowException
    • put(byte[] bs, int offset, int length) 从当前position槽位写入字节数组,从数组的offset偏移位置取,共写入length个
    • put(int index, byte b) 向指定index槽位写入数据
    • put(ByteBuffer buffer) 转换参数ByteBuffer

    ByteBuffer除了基本的字节读写操作,还提供了更便利的get变体操作,完成以不同数据类型读写ByteBuffer,如:

    • getChar 以字符形式读取ByteBuffer
    • getInt 以int形式读取ByteBuffer
    • putChar 以字符形式写入Buffer
    • putInt 以int形式写入Buffer

    除了以上的变体操作,还有很多其他的数据类型的get/put变体操作,可以查看ByteBuffer详细的api。

    ByteBuffer除了基本的读写操作,还有几个继承自Buffer父类的行为,Buffer的行为在其派生出的子类中都具有:

    • isDirect:是否为直接Buffer(后续文章中会说明直接的含义)
    • isReadOnly:是否为只读
    • hasRemaining:是否存在剩余容量
    • remaining:剩余容量大小

    Buffer分类

    在以ByteBuffer为例介绍完,Buffer的常用行为后,下面在简单的了解下Nio中提供的Buffer类型,以及与ByteBuffer的区别。

    Nio中的Buffer可以从多维角度进行划分:1.数据类型;2.堆Buffer和直接内存Buffer;3.抽想Buffer和其对应的具体实现

    1) 数据类型
    按照存储的数据类型,可以将Buffer划分为:

    • ByteBuffer: 存储字节
    • ShortBuffer:存储短整型
    • IntBuffer:存储整形
    • LongBuffer:存储长整型
    • CharBuffer:存储字符类型
    • FloatBuffer:存储浮点类型
    • DoubleBuffer:存储双精度

    ByteBuffer提供get/put的变体,以方便的api实现其他数据类型的读写,其他类型的Buffer均只有基本数据类型的get/put读写。

    2) 堆/直接内存
    按照数据的存储位置,可以将Buffer分为堆Buffer和直接内存Buffer。

    • 堆Buffer:Buffer内存分配在Java内存的堆区域,Heap Buffer实现:
      HeapByteBuffer/HeapCharBuffer等

    • 直接内存Buffer:Buffer使用堆外的直接内存,如:Direct Buffer实现:
      DirectByteBuffer/DirectCharBuffer

    3) 抽象Buffer和其对应实现
    Nio中Buffer的设计模式非常强,工厂模式、。。。我回头再温故下设计模式,嘿嘿。
    Nio中利用面向对象的多态性,使用多重继承将Buffer模块设计的鬼斧神工。

    对每种数据类型的Buffer都定义其抽象的数据类型Buffer,如:
    ByteBuffer/ShortBuffer/IntBuffer等抽象类,然后针对堆Buffer和直接内存Buffer两种形态又设计各自的实现:HeapByteBuffer/DirectByteBuffer、HeapShortBuffer/DirectShortBuffer、HeapIntBuffer/DirectIntBuffer。

    总结

    至此,Buffer的类容介绍完毕。这里只是浅谈了Buffer的原理、行为和分类,并没有对源码进行解读,感兴趣的朋友可以自行阅读。

    这里再做下简单的总结,各种类型的Buffer主要是利用线性数组存储各种类型的数据,维护多个属性位,伴随将属性位变化达到读写状态的变化。读写操作的多态形式使得Buffer的api机器丰富,可用性极强。Buffer的种类可谓纷繁,每种Buffer都有其各自的应用场景,解决不同的需求。

    参考

    java NIO详解
    NIO学习–缓冲区
    Java 语言中一个字符占几个字节

  • 相关阅读:
    增加文章
    网站之注册
    C#常用的引用
    Session.Abandon和Session.Clear有何不同 (转)
    C#文件路径的写法
    UpdatePanel的用法详解
    [转]asp:ScriptManager
    Git 常用命令
    AJAX请求 $.post方法的使用
    a 标签中调用js的几种方法
  • 原文地址:https://www.cnblogs.com/lxyit/p/9114503.html
Copyright © 2011-2022 走看看