`
kakajw
  • 浏览: 265330 次
  • 性别: 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,写的很好。

相关推荐

    nio.zip_NIO_NewIO_NIO.c_java NIO chm_java nio

    `nio.c`可能是指C语言实现的NIO相关代码,这在Java NIO中不太常见,但可能是为了演示底层I/O操作的原理或与Java NIO进行性能比较。在Java中,NIO API通常由Java标准库提供,无需使用C代码。 `java_nio_chm`指的是...

    Java-NIO-Programming-Cookbook(含源码)

    在Java NIO中,`ByteBuffer`是核心类之一,它用于存储和读写数据。`DirectByteBuffer`是`ByteBuffer`的一个实现,它与`Non-Direct Buffer`(即堆缓冲区)有所不同。直接缓冲区在Java中使用JNI(Java Native ...

    Java NIO 主要类和方法(Java NIO中文版 附录C)

    Java NIO(New Input/Output)是Java提供的一种新的输入输出处理机制,它与传统IO基于流的处理方式不同,NIO支持面向缓冲区的(Buffer-oriented)、基于通道的(Channel-based)I/O操作。在Java NIO中,Buffer类是...

    Java NIO入门的源码

    4. **文件系统接口(File Systems)**:Java NIO提供了更底层的文件系统访问方式,例如FileChannel和Files类,它们提供了读写文件、映射文件到内存等操作。 5. **字符集(Charsets)**:NIO提供了更强大的字符集支持,...

    Java Nio ibm技术文档

    本文档旨在为Java程序员提供一个全面深入理解NIO库的教程,不仅涵盖了概念性的高级知识,还包括了底层编程细节。 #### 二、NIO概述 ##### 2.1 历史背景 - **原生I/O**:Java最初提供的I/O操作是基于流的,这在处理...

    DatagramChannelImpl.rar_java nio

    通过阅读`DatagramChannelImpl.java`源代码,开发者可以更深入地理解Java NIO如何在底层处理UDP通信,以及如何利用NIO的特性提高应用程序的性能。不过,由于`DatagramChannelImpl`是Java的内部实现,其源代码可能...

    JavaNIO.pdf

    Java NIO(New I/O,非阻塞I/O)是Java提供的一种用于替代标准Java I/O ...Java NIO作为Java平台上的一个重要I/O库,它为开发者提供了更多控制底层网络和文件I/O操作的能力,尤其是在需要高性能和高并发处理的场景中。

    Java NIO非阻塞服务器示例.docx

    Java NIO(New IO)是Java 从JDK 1.4版本开始引入的一个新特性,它提供了非阻塞I/O操作的能力,极大地提升了Java在处理网络通信时的效率。在传统的Java IO模型中,每个连接都需要一个独立的线程进行处理,这在面对...

    JavaNIO中(英)文书籍

    - **通道(Channels)**:NIO的核心组件之一,提供了从一个数据源到另一个数据源的双向传输数据的能力。例如,FileChannel可以用于文件读写,SocketChannel则用于网络通信。 - **缓冲区(Buffers)**:数据在传输...

    Android中的ByteBuffer解析

    ByteBuffer是Java NIO(Non-Blocking I/O)的一部分,允许开发者高效地读写大量原始字节,如处理图像、音频或网络数据。本文将深入探讨Android中ByteBuffer的工作原理及其常见应用场景。 一、ByteBuffer的基础概念 ...

    JAVANIO简介.pdf

    ### JAVANIO简介 #### 一、基本概念与传统IO 在计算机科学领域中,**IO(Input/Output)**是指主存与外部设备(如硬盘、终端、网络等)之间传输数据的过程。它是操作系统的一项核心功能,通过专用的I/O指令来实现。...

    主题JAVANIO简介知识点.pdf

    ### 主题JAVANIO简介知识点 #### 一、基本概念与Java标准IO回顾 **基本概念** 在计算机科学中,I/O(Input/Output,输入/输出)是指主存和外部设备(如硬盘、终端、网络等)之间拷贝数据的过程。I/O操作是操作...

    NIO(java)原生实现,没有用任何框架

    在NIO中,数据的读写主要通过ByteBuffer进行,缓冲区是NIO的核心组件之一。这个类可能实现了缓冲区的分配、存储和释放,以及一些辅助功能,如获取缓冲区中的数据或者将数据写入缓冲区。 在Java NIO的网络编程中,...

    快速了解Java中NIO核心组件

    Channel 是 Java NIO 提供的一座桥梁,用于我们的程序和操作系统底层 I/O 服务进行交互。Channel 使用起来跟 Stream 相似,可以读取数据到 Buffer 中,也可以把 Buffer 中的数据写入通道。 在 Java NIO 中,常用的 ...

Global site tag (gtag.js) - Google Analytics