专栏首页用户2442861的专栏Java的NIO之ByteBuffer底层分析

Java的NIO之ByteBuffer底层分析

http://kakajw.iteye.com/blog/1797073

类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容量)组成。不多说,上源码:

Java代码  

 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存放数据。

Java代码  

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

底层源码的实现如下

Java代码  

  1. class HeapByteBuffer  
  2. extends ByteBuffer  
  3. {  
  4.     ......  
  5. public ByteBuffer put(byte x) {  
  6.       hb[ix(nextPutIndex())] = x;  
  7. return this;  
  8.     }  
  9.     ......  
  10. final int nextPutIndex() {      
  11. if (position >= limit)  
  12. throw new BufferOverflowException();  
  13. return position++;  
  14.     }  
  15.     ......  
  16. }  

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

get()方法相似;

Java代码  

  1. public byte get() {  
  2. return hb[ix(nextGetIndex())];  
  3. }  

4个指针的涵义

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

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

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

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

  ByteBuffer结构如下图所示

四. ByteBuffer的关键方法实现

  1.取元素

Java代码  

  1. public abstract byte get();  
  2. //HeapByteBuffer子类实现
  3. public byte get() {  
  4. return hb[ix(nextGetIndex())];  
  5. }  
  6. //HeapByteBuffer子类方法
  7. final int nextGetIndex() {                
  8. if (position >= limit)  
  9. throw new BufferUnderflowException();  
  10. return position++;  
  11. }   

2.存元素

Java代码  

  1. public abstract ByteBuffer put(byte b);  
  2. //HeapByteBuffer子类实现
  3. public ByteBuffer put(byte x) {  
  4.      hb[ix(nextPutIndex())] = x;  
  5. return this;  
  6.  }  

  3.清除数据   

Java代码  

  1. public final Buffer clear() {  
  2.     position = 0;  
  3.     limit = capacity;  
  4.     mark = -1;  
  5. return this;  
  6. }  

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

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

Java代码  

  1. public final byte[] array() {  
  2. if (hb == null)  
  3. throw new UnsupportedOperationException();  
  4. if (isReadOnly)  
  5. throw new ReadOnlyBufferException();  
  6. return hb;  
  7. }  

  5.flip-位置重置

Java代码  

  1. public final Buffer flip() {  
  2.     limit = position;  
  3.     position = 0;  
  4.     mark = -1;  
  5. return this;  
  6. }  

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

Java代码  

  1. public final Buffer rewind() {  
  2.      position = 0;  
  3.      mark = -1;  
  4. return this;  
  5. }  

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

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

Java代码  

  1. public final int remaining() {  
  2. return limit - position;  
  3. }  

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

 8.标记

Java代码  

  1. public final Buffer mark() {  
  2.     mark = position;  
  3. return this;  
  4. }  

  9.重置到标记

Java代码  

  1. public final Buffer reset() {  
  2. int m = mark;  
  3. if (m < 0)  
  4. throw new InvalidMarkException();  
  5.     position = m;  
  6. return this;  
  7. }  

五.创建ByteBuffer对象的方式

   1.allocate方式

Java代码  

  1. public static ByteBuffer allocate(int capacity) {  
  2. if (capacity < 0)  
  3. throw new IllegalArgumentException();  
  4. return new HeapByteBuffer(capacity, capacity);  
  5. }  
  6. HeapByteBuffer(int cap, int lim) {  // package-private
  7. super(-1, 0, lim, cap, new byte[cap], 0);  
  8. /*
  9.      hb = new byte[cap];
  10.      offset = 0;
  11.      */
  12. }  
  13. // Creates a new buffer with the given mark, position, limit, capacity,
  14. // backing array, and array offset
  15. //
  16. ByteBuffer(int mark, int pos, int lim, int cap, // package-private
  17. byte[] hb, int offset)  
  18. {  
  19. super(mark, pos, lim, cap);  
  20. this.hb = hb;  
  21. this.offset = offset;  
  22. }  
  23. // Creates a new buffer with the given mark, position, limit, and capacity,
  24. // after checking invariants.
  25. //
  26. Buffer(int mark, int pos, int lim, int cap) { // package-private
  27. if (cap < 0)  
  28. throw new IllegalArgumentException();  
  29. this.capacity = cap;  
  30.      limit(lim);  
  31.      position(pos);  
  32. if (mark >= 0) {  
  33. if (mark > pos)  
  34. throw new IllegalArgumentException();  
  35. this.mark = mark;  
  36.      }  
  37.  }  
  38. p;  

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

   2.wrap方式

Java代码  

  1. public static ByteBuffer wrap(byte[] array) {  
  2. return wrap(array, 0, array.length);  
  3.    }  
  4. public static ByteBuffer wrap(byte[] array,  
  5. int offset, int length)  
  6.    {  
  7. try {  
  8. return new HeapByteBuffer(array, offset, length);  
  9.        } catch (IllegalArgumentException x) {  
  10. throw new IndexOutOfBoundsException();  
  11.        }  
  12.    }  
  13.    HeapByteBuffer(byte[] buf, int off, int len) { // package-private
  14. super(-1, off, off + len, buf.length, buf, 0);  
  15. /*
  16.        hb = buf;
  17.        offset = 0;
  18.        */
  19.     }  

   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的四个指针如何移动有了清晰的认识。

Java NIO学习笔记之二-图解ByteBuffer

概述

ByteBuffer是NIO里用得最多的Buffer,它包含两个实现方式:HeapByteBuffer是基于Java堆的实现,而DirectByteBuffer则使用了unsafe的API进行了堆外的实现。这里只说HeapByteBuffer。

使用

ByteBuffer最核心的方法是put(byte)get()。分别是往ByteBuffer里写一个字节,和读一个字节。

值得注意的是,ByteBuffer的读写模式是分开的,正常的应用场景是:往ByteBuffer里写一些数据,然后flip(),然后再读出来。

这里插两个Channel方面的对象,以便更好的理解Buffer。

ReadableByteChannel是一个从Channel中读取数据,并保存到ByteBuffer的接口,它包含一个方法:

<!-- lang: java -->
public int read(ByteBuffer dst) throws IOException;

WritableByteChannel则是从ByteBuffer中读取数据,并输出到Channel的接口:

<!-- lang: java -->
public int write(ByteBuffer src) throws IOException;

那么,一个ByteBuffer的使用过程是这样的:

<!-- lang: java -->
byteBuffer = ByteBuffer.allocate(N);
//读取数据,写入byteBuffer
readableByteChannel.read(byteBuffer);
//变读为写
byteBuffer.flip();
//读取byteBuffer,写入数据
writableByteChannel.write(byteBuffer);

看到这里,一般都不太明白flip()干了什么事,先从ByteBuffer结构说起:

ByteBuffer内部字段

byte[] buff

buff即内部用于缓存的数组。

position

当前读取的位置。

mark

为某一读过的位置做标记,便于某些时候回退到该位置。

capacity

初始化时候的容量。

limit

读写的上限,limit<=capacity。

图解

put

写模式下,往buffer里写一个字节,并把postion移动一位。写模式下,一般limit与capacity相等。 

flip

写完数据,需要开始读的时候,将postion复位到0,并将limit设为当前postion。 

get

从buffer里读一个字节,并把postion移动一位。上限是limit,即写入数据的最后位置。 

clear

将position置为0,并不清除buffer内容。 

mark相关的方法主要是mark()(标记)和reset()(回到标记),比较简单,就不画图了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 2014网易实习生招聘面试题

    http://blog.csdn.net/silangquan/article/details/18969875

    bear_fish
  • 最大的子序列和问题

    http://blog.csdn.net/zhutulang/article/details/7505785

    bear_fish
  • ubuntu less分页查看log文件

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haluoluo211/article/details...

    bear_fish
  • O_DIRECT方式读取文件示例

    一见
  • Q191 Number of 1 Bits

    Write a function that takes an unsigned integer and returns the number of ’1' bi...

    echobingo
  • Linux 属组权限 网络设置

    /etc/sysconfig/network-scripts/ifcfg-etc0

    A2Data
  • 用wget同步ftp

    wget ftp://user:password@ftp.mysite.com:myport/ftp/ -r -x -c

    力哥聊运维与云计算
  • C#7.3 新增功能

    C# 7.3 版本有两个主要主题。 第一个主题提供使安全代码的性能与不安全代码的性能一样好的功能。 第二个主题提供对现有功能的增量改进。 此外,在此版本中添加了...

    张传宁老师
  • Windows Mobile上的蓝牙点对点通信

    实验室做短距离通信,蓝牙是其中主要技术之一。研究生入学复试的时候,大老板跟我们说,蓝牙的起源和一位弹钢琴的人有关,因为蓝牙用到了跳频,这个和手指在各个琴键之间跳...

    ShiJiong
  • LintCode 最长公共子串代码

    desperate633

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动