`
kakajw
  • 浏览: 263017 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Java的NIO之ByteBuffer底层分析

阅读更多

类ByteBuffer是Java nio程序经常会用到的类,也是重要类 ,我们通过源码分析该类的实现原理。


一.ByteBuffer类的继承结构

public abstract class ByteBuffer
extends Buffer
implements Comparable<ByteBuffer>

 

ByteBuffer的核心特性来自Buffer


二. ByteBuffer和Buffer的核心特性
A container for data of a specific primitive type. 用于特定基本类型数据的容器。
子类ByteBuffer支持除boolean类型以外的全部基本数据类型。


补充,回顾Java的基本数据类型

Java语言提供了八种基本类型,六种数字类型(四个整数型,两个浮点型),一种字符类型,一种布尔型。

1、整数:包括int,short,byte,long
2、浮点型:float,double
3、字符:char
4、布尔:boolean

 

类型    大小 最小值   最大值
byte    8-bit -128   +127
short  16-bit -2^15   +2^15-1
int       32-bit -2^31   +2^31-1
long    64-bit -2^63   +2^63-1
float    32-bit IEEE754   IEEE754
double 64-bit IEEE754   IEEE754
char    16-bit Unicode 0 Unicode 2^16-1
boolean ----- -----   ------     


本质上,Buffer也就是由装有特定基本类型数据的一块内存缓冲区和操作数据的4个指针变量(mark标记,position位置, limit界限,capacity容量)组成。不多说,上源码:

public abstract class Buffer {

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

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

    // These fields are declared here rather than in Heap-X-Buffer in order to
    // reduce the number of virtual method invocations needed to access these
    // values, which is especially costly when coding small buffers.
    //
    final byte[] hb;   // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;   // Valid only for heap buffers
    ......
}

 其中,字节数组final byte[] hb就是所指的那块内存缓冲区。


Buffer缓冲区的主要功能特性有:
a.Transferring data  数据传输,主要指可通过get()方法和put()方法向缓冲区存取数据,ByteBuffer提供存取除boolean以为的全部基本类型数据的方法。


b.Marking and resetting  做标记和重置,指mark()方法和reset()方法;而标记,无非是保存操作中某个时刻的索引位置。


c.Invariants 各种指针变量


d.Clearing, flipping, and rewinding 清除数据,位置(position)置0(界限limit为当前位置),位置(position)置0(界限limit不变),指clear()方法, flip()方法和rewind()方法。

 

e.Read-only buffers 只读缓冲区,指可将缓冲区设为只读。

 

f.Thread safety 关于线程安全,指该缓冲区不是线程安全的,若多线程操作该缓冲区,则应通过同步来控制对该缓冲区的访问。


g.Invocation chaining 调用链, 指该类的方法返回调用它们的缓冲区,因此,可将方法调用组成一个链;例如:
 b.flip();
 b.position(23);
 b.limit(42);
等同于
 b.flip().position(23).limit(42);

 


三.ByteBuffer的结构

ByteBuffer主要由是由装数据的内存缓冲区和操作数据的4个指针变量(mark标记,position位置, limit界限,capacity容量)组成。
内存缓冲区:字节数组final byte[] hb;
ByteBuffer的主要功能也是由这两部分配合实现的,如put()方法,就是向数组byte[] hb存放数据。

    ByteBuffer bb = ByteBuffer.allocate(10); 
    // 向bb装入byte数据
    bb.put((byte)9);

 
底层源码的实现如下

class HeapByteBuffer
    extends ByteBuffer
{
    ......
    public ByteBuffer put(byte x) {
      hb[ix(nextPutIndex())] = x;
      return this;
    }
    
    ......
    final int nextPutIndex() {    
      if (position >= limit)
      throw new BufferOverflowException();
       return position++;
    }
    ......
}

 

如上所述,bb.put((byte)9);执行时,先判断position 是否超过 limit,否则指针position向前移一位,将字节(byte)9存入position所指byte[] hb索引位置。

get()方法相似;

    public byte get() {
       return hb[ix(nextGetIndex())];
    }

 

4个指针的涵义

position:位置指针。微观上,指向底层字节数组byte[] hb的某个索引位置;宏观上,是ByteBuffer的操作位置,如get()完成后,position指向当前(取出)元素的下一位,put()方法执行完成后,position指向当前(存入)元素的下一位;它是核心位置指针。

 

mark标记:保存某个时刻的position指针的值,通过调用mark()实现;当mark被置为负值时,表示废弃标记。

 

capacity容量:表示ByteBuffer的总长度/总容量,也即底层字节数组byte[] hb的容量,一般不可变,用于读取。

 

limit界限:也是位置指针,表示待操作数据的界限,它总是和读取或存入操作相关联,limit指针可以被  改变,可以认为limit<=capacity。

 

  ByteBuffer结构如下图所示



 

 

 

四. ByteBuffer的关键方法实现

  1.取元素

    public abstract byte get();

    //HeapByteBuffer子类实现
    public byte get() {
       return hb[ix(nextGetIndex())];
    }


    //HeapByteBuffer子类方法
    final int nextGetIndex() {				
        if (position >= limit)
	    throw new BufferUnderflowException();
       return position++;
    } 

  2.存元素

   public abstract ByteBuffer put(byte b);

    //HeapByteBuffer子类实现
    public ByteBuffer put(byte x) {
        hb[ix(nextPutIndex())] = x;
        return this;
    }

    
  3.清除数据   

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

     
    可见,对于clear()方法,ByteBuffer只是重置position指针和limit指针,废弃mark标记,并没有真正清空缓冲区/底层字节数组byte[] hb的数据;
    ByteBuffer也没有提供真正清空缓冲区数据的接口,数据总是被覆盖而不是清空。
    例如,对于Socket读操作,若从socket中read到数据后,需要从头开始存放到缓冲区,而不是从上次的位置开始继续/连续存放,则需要clear(),重置position指针,但此时需要注意,若read到的数据没有填满缓冲区,则socket的read完成后,不能使用array()方法取出缓冲区的数据,因为array()返回的是整个缓冲区的数据,而不是上次read到的数据。


  4. 以字节数组形式返回整个缓冲区的数据/byte[] hb的数据

    public final byte[] array() {
        if (hb == null)
	    throw new UnsupportedOperationException();
         if (isReadOnly)
            throw new ReadOnlyBufferException();
         return hb;
    }

 

  5.flip-位置重置

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

    socket的read操作完成后,若需要write刚才read到的数据,则需要在write执行前执行flip(),以重置操作位置指针,保存操作数据的界限,保证write数据准确。   
    
 6.rewind-位置重置

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

   Rewinds this buffer. The position is set to zero and the mark is discarded.
  和flip()相比较而言,没有执行limit = position;

 

7.判断剩余的操作数据或者剩余的操作空间

    public final int remaining() {
        return limit - position;
    }

   常用于判断socket的write操作中未写出的数据;

 

 8.标记

    public final Buffer mark() {
        mark = position;
        return this;
    }

   
  9.重置到标记

    public final Buffer reset() {
        int m = mark;
        if (m < 0)
          throw new InvalidMarkException();
        position = m;
        return this;
    }

 
五.创建ByteBuffer对象的方式

   1.allocate方式

    public static ByteBuffer allocate(int capacity) {
      if (capacity < 0)
          throw new IllegalArgumentException();
          return new HeapByteBuffer(capacity, capacity);
    }

    HeapByteBuffer(int cap, int lim) {  // package-private
         super(-1, 0, lim, cap, new byte[cap], 0);
         /*
         hb = new byte[cap];
         offset = 0;
         */
    }


    // Creates a new buffer with the given mark, position, limit, capacity,
    // backing array, and array offset
    //
    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;
    }


    // Creates a new buffer with the given mark, position, limit, and capacity,
    // after checking invariants.
    //
    Buffer(int mark, int pos, int lim, int cap) { // package-private
        if (cap < 0)
          throw new IllegalArgumentException();
         this.capacity = cap;
         limit(lim);
         position(pos);
         if (mark >= 0) {
           if (mark > pos)
              throw new IllegalArgumentException();
           this.mark = mark;
         }
     }
 

    由此可见,allocate方式创建ByteBuffer对象的主要工作包括: 新建底层字节数组byte[] hb(长度为capacity),mark置为-1,position置为0,limit置为capacity,capacity为用户指定的长度。

 

   2.wrap方式

 public static ByteBuffer wrap(byte[] array) {
	return wrap(array, 0, array.length);
    }


    public static ByteBuffer wrap(byte[] array,
				    int offset, int length)
    {
        try {
           return new HeapByteBuffer(array, offset, length);
        } catch (IllegalArgumentException x) {
           throw new IndexOutOfBoundsException();
        }
    }

    HeapByteBuffer(byte[] buf, int off, int len) { // package-private
         super(-1, off, off + len, buf.length, buf, 0);
        /*
        hb = buf;
        offset = 0;
        */
     }

   wrap方式和allocate方式本质相同,不过因为由用户指定的参数不同,参数为byte[] array,所以不需要新建字节数组,byte[] hb置为byte[] array,mark置为-1,position置为0,limit置为array.length,capacity置为array.length。

 

       六、结论

        由此可见,ByteBuffer的底层结构清晰,不复杂,源码仍是弄清原理的最佳文档。
读完此文,应该当Java nio的SocketChannel进行read或者write操作时,ByteBuffer的四个指针如何移动有了清晰的认识。

  • 大小: 11.4 KB
分享到:
评论
1 楼 mengsina 2014-02-27  
mark,写的很好。

相关推荐

    java NIO和java并发编程的书籍

    java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java...

    JAVA NIO 按行读取大文件支持 GB级别-修正版

    设计思想: 每次通过nio读取字节到 fbb中 然后对fbb自己中的内容进行行判断即 10 回车 13 行号 0 文件结束 这样字节的判断,然后 返回行 如果 到达 fbb的结尾 还没有结束,就再通过nio读取一段字节,继续处理。 ...

    JavaNIO chm帮助文档

    Java NIO系列教程(一) Java NIO 概述 Java NIO系列教程(二) Channel Java NIO系列教程(三) Buffer Java NIO系列教程(四) Scatter/Gather Java NIO系列教程(五) 通道之间的数据传输 Java NIO系列教程(六)...

    NIO(byteBuffer)按行读取文件

    使用nio byteBuffer 实现按行读取文件(大文件) 在window/linux/macOS上均测试通过 对于中文乱码也已处理成功 完整注释,可随需求更改 有问题请邮件:mly610865580@126.com

    Java NIO英文高清原版

    Java NIO英文高清原版

    java NIO 中文版

    讲解了 JavaIO 与 JAVA NIO区别,JAVA NIO设计理念,以及JDK中java NIO中语法的使用

    Java NIO 中文 Java NIO 中文 Java NIO 中文文档

    Java NIO 深入探讨了 1.4 版的 I/O 新特性,并告诉您如何使用这些特性来极大地提升您所写的 Java 代码的执行效率。这本小册子就程序员所面临的有代表性的 I/O 问题作了详尽阐述,并讲解了 如何才能充分利用新的 I/O ...

    java nio 包读取超大数据文件

    Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据...

    java NIO 视频教程

    Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。 Java NIO: Channels and Buffers(通道和缓冲区) 标准的IO基于字节流和字符流进行操作的,...

    java nio 实现socket

    java nio 实现socketjava nio 实现socketjava nio 实现socketjava nio 实现socketjava nio 实现socket

    java nio中文版

    java NIO是 java New IO 的简称,在 jdk1.4 里提供的新 api 。 Sun 官方标榜的特性如下: – 为所有的原始类型提供 (Buffer) 缓存支持。 – 字符集编码解码解决方案。 – Channel :一个新的原始 I/O 抽象。 – 支持...

    java NIO技巧及原理

    java NIO技巧及原理解析,java IO原理,NIO框架分析,性能比较

    Java NIO原理 图文分析及代码实现

    Java NIO原理 图文分析及代码实现

    Java NIO与IO性能对比分析.pdf

    Java NIO与IO性能对比分析.pdf

    Java Nio selector例程

    java侧起server(NioUdpServer1.java),基于Java Nio的selector 阻塞等候,一个android app(NioUdpClient1文件夹)和一个java程序(UI.java)作为两个client分别向该server发数据,server收到后分别打印收到的消息...

    java基于NIO实现Reactor模型源码.zip

    java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现...

    java NIO.zip

    java NIO.zip

    java nio 读文件

    java nio 读文件,java nio 读文件

    JAVA NIO 学习资料

    JAVA NIO学习资料JAVA NIO学习资料

    Chronicle-Bytes:Chronicle Bytes具有与Java NIO的ByteBuffer类似的用途,具有许多扩展

    Chronicle Bytes具有与Java NIO的ByteBuffer类似的用途,但具有一些扩展。 API支持。 64位大小 UTF-8和ISO-8859-1编码的字符串。 线程安全关闭堆内存操作。 通过引用计数确定性地释放资源。 压缩数据类型,...

Global site tag (gtag.js) - Google Analytics