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代码
底层源码的实现如下
Java代码
如上所述,bb.put((byte)9);执行时,先判断position 是否超过 limit,否则指针position向前移一位,将字节(byte)9存入position所指byte[] hb索引位置。
get()方法相似;
Java代码
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代码
2.存元素
Java代码
3.清除数据
Java代码
可见,对于clear()方法,ByteBuffer只是重置position指针和limit指针,废弃mark标记,并没有真正清空缓冲区/底层字节数组byte[] hb的数据; ByteBuffer也没有提供真正清空缓冲区数据的接口,数据总是被覆盖而不是清空。 例如,对于Socket读操作,若从socket中read到数据后,需要从头开始存放到缓冲区,而不是从上次的位置开始继续/连续存放,则需要clear(),重置position指针,但此时需要注意,若read到的数据没有填满缓冲区,则socket的read完成后,不能使用array()方法取出缓冲区的数据,因为array()返回的是整个缓冲区的数据,而不是上次read到的数据。
4. 以字节数组形式返回整个缓冲区的数据/byte[] hb的数据
Java代码
5.flip-位置重置
Java代码
socket的read操作完成后,若需要write刚才read到的数据,则需要在write执行前执行flip(),以重置操作位置指针,保存操作数据的界限,保证write数据准确。 6.rewind-位置重置
Java代码
Rewinds this buffer. The position is set to zero and the mark is discarded. 和flip()相比较而言,没有执行limit = position;
7.判断剩余的操作数据或者剩余的操作空间
Java代码
常用于判断socket的write操作中未写出的数据;
8.标记
Java代码
9.重置到标记
Java代码
五.创建ByteBuffer对象的方式
1.allocate方式
Java代码
由此可见,allocate方式创建ByteBuffer对象的主要工作包括: 新建底层字节数组byte[] hb(长度为capacity),mark置为-1,position置为0,limit置为capacity,capacity为用户指定的长度。
2.wrap方式
Java代码
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结构说起:
buff即内部用于缓存的数组。
当前读取的位置。
为某一读过的位置做标记,便于某些时候回退到该位置。
初始化时候的容量。
读写的上限,limit<=capacity。
写模式下,往buffer里写一个字节,并把postion移动一位。写模式下,一般limit与capacity相等。
写完数据,需要开始读的时候,将postion复位到0,并将limit设为当前postion。
从buffer里读一个字节,并把postion移动一位。上限是limit,即写入数据的最后位置。
将position置为0,并不清除buffer内容。
mark相关的方法主要是mark()
(标记)和reset()
(回到标记),比较简单,就不画图了。