原文地址: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代码  
  1. public abstract class Buffer {
  2. // Invariants: mark <= position <= limit <= capacity
  3. private int mark = -1;
  4. private int position = 0;
  5. private int limit;
  6. private int capacity;
  7. ......
  8. }
  9. public abstract class ByteBuffer
  10. extends Buffer
  11. implements Comparable<ByteBuffer>
  12. {
  13. // These fields are declared here rather than in Heap-X-Buffer in order to
  14. // reduce the number of virtual method invocations needed to access these
  15. // values, which is especially costly when coding small buffers.
  16. //
  17. final byte[] hb;   // Non-null only for heap buffers
  18. final int offset;
  19. boolean isReadOnly;   // Valid only for heap buffers
  20. ......
  21. }

其中,字节数组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之ByteBuffer详解相关推荐

  1. java nio详解,Java NIO API详解

    Java NIO API详解 在JDK 1.4以前,Java的IO操作集中在java.io这个包中,是基于流的阻塞(blocking)API.对于大多数应用来说,这样的API使用很方 便,然而,一些对 ...

  2. ByteBuffer详解(大概2333)

    ByteBuffer详解 ByteBuffer详解 概述 ByteBuffer属性 capacity limit position Mark ByteBuffer方法 allocate() order ...

  3. java IO编程详解

    java IO编程详解 一.Socket 1. Sock概述 Socket,套接字就是两台主机之间逻辑连接的端点.TCP/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP协议是应用层协议 ...

  4. Java内存溢出详解之Tomcat配置

    Java内存溢出详解 转自:http://elf8848.iteye.com/blog/378805 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError ...

  5. java基础(十三)-----详解内部类——Java高级开发必须懂的

    java基础(十三)-----详解内部类--Java高级开发必须懂的 目录 为什么要使用内部类 内部类基础 静态内部类 成员内部类 成员内部类的对象创建 继承成员内部类 局部内部类 推荐博客 匿名内部 ...

  6. Java类加载机制详解【java面试题】

    Java类加载机制详解[java面试题] (1)问题分析: Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数 ...

  7. Java线程池详解学习:ThreadPoolExecutor

    Java线程池详解学习:ThreadPoolExecutor Java的源码下载参考这篇文章:Java源码下载和阅读(JDK1.8) - zhangpeterx的博客 在源码的目录java/util/ ...

  8. Java 线程池详解学习:FixedThreadPool,CachedThreadPool,ScheduledThreadPool...

    Java常用的线程池有FixedThreadPool和CachedThreadPool,我们可以通过查看他们的源码来进行学习. Java的源码下载参考这篇文章:Java源码下载和阅读(JDK1.8) ...

  9. 关于Java的Classpath详解

    关于Java的Classpath详解 Java 的新入门者对classpath往往比较困惑,为何在开发环境中能运行的东东出去就不好,或在外面运行的东东挺溜的进了开发环境就死菜. java的优点就是他是 ...

最新文章

  1. 【整理总结】Visual Studio 扩展和更新
  2. 数据产品--浅析如何搭建维度指标系统
  3. 太和二中计算机考试,安徽省太和二中高二数学下册期末考试试题精选
  4. windows下面虚拟主机
  5. Kubernetes在上汽集团云平台及AI方面的应用
  6. linux环境编程做的是什么,Linux环境
  7. HP-UX 中配置Trusted System
  8. 一篇不大正经的关于数论的总结(未完
  9. 在pycharm运行正常,在命令行窗口出现引包错误
  10. webpack多环境(dev stg prd qa)打包问题
  11. 麟龙指标通达信指标公式源码_【通达信】麟龙量能饱合度指标公式源码
  12. 黑马python5_黑马Python5.0+人工智能课程升级5.0版本!【完整无秘】
  13. ckplayer ajax,谁能帮我做一个脚本啊?能让这个网页视频播放可以拉动进度条 可以快进...
  14. 计算机与应用在线作业答案,计算机应用基础在线作业及答案
  15. 《卓有成效的管理者》笔记,第一章 卓有成效是可以学会的
  16. BLE蓝牙的广播类型
  17. 永恒之蓝漏洞复现-CVE-2017-0144
  18. 米兔机器人自主编程_米兔积木机器人编程指南.pdf
  19. iOS开发-点击屏幕,键盘消失的极佳方法。
  20. MakerDAO中国区负责人 潘超:在加密世界研究经济学

热门文章

  1. 第一阶 08 单例模式、递归、方法参数和this等关键字
  2. 猜拳游戏(基于python面向对象2)
  3. 解决Could not determine artifacts for XXXX: Skipped due to earlier error
  4. 使用网络打印机进行“属性”和“首选项”设置时,弹出个浏览器.exe错误对话框
  5. HTML5 代码规范
  6. mysql学校教务系统_java servlet+mysql开发的学校官网+教务系统+图书馆系统,功能完善,供参考学习...
  7. NOI Online2022 入门组题解
  8. linux常用命令及其python调用
  9. win10浏览器加载很慢_win10加载网页很慢_win10打开浏览器加载网页很慢处理技巧...
  10. warning #179-D:variable XXX was declared but never referenced