大家好,又见面了,我是你们的朋友全栈君。
最近再看java的NIO,里面提到了几个基本的类,其中ByteBuffer是最基础的,用于Channel的读写传输数据使用。下面总结一下我理解的ByteBuffer。 先从代码开始分析
static public void asIntBuffer() {
ByteBuffer bBuf = ByteBuffer.allocate(512);
bBuf.putInt(1);
bBuf.putInt(2);
bBuf.putInt(3);
bBuf.putInt(4);
bBuf.putInt(5);
bBuf.putInt(6);
bBuf.putInt(7);
bBuf.flip();
bBuf.putInt(8);
bBuf.putInt(9);
System.out.println("缓冲区Pos:" + bBuf.position() + " 缓冲区Limit:"
+ bBuf.limit());
System.out.println(bBuf.getInt());
System.out.println(bBuf.getInt());
System.out.println(bBuf.getInt());
System.out.println(bBuf.getInt());
System.out.println(bBuf.getInt());
}
输出:
缓冲区Pos:8 缓冲区Limit:28
3
4
5
6
7
从上面的输出发现当flip()被调用之后如果在网buffer里面put数据会覆盖之前写入的数据,导致Position位置后移,如果在加一句get()就会出现java.nio.BufferUnderflowException异常,见下面的输出。
缓冲区Pos:8 缓冲区Limit:28
3
4
5
6
7
Exception in thread "main" java.nio.BufferUnderflowException
at java.nio.Buffer.nextGetIndex(Buffer.java:498)
at java.nio.HeapByteBuffer.getInt(HeapByteBuffer.java:355)
at com.Demo.asIntBuffer(Demo.java:52)
at com.Demo.main(Demo.java:22)
简单的分析一下put、get和flip的源代码。
ByteBuffer bBuf = ByteBuffer.allocate(512);
首先看allocate函数,通过传入一个capacity用来指定buffer的容量,返回了一个HeapByteBuffer的对象,该对象是ByteBuffer的一个子类,其构造函数直接调用了ByteBuffer的构造函数。
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
HeapByteBuffer的构造函数:
HeapByteBuffer(int cap, int lim) { // package-private
super(-1, 0, lim, cap, new byte[cap], 0);//初始化 limit pos cap mark等参数
/*
hb = new byte[cap];
offset = 0;
*/
}
HeapByteBuffer(byte[] buf, int off, int len) { // package-private
super(-1, off, off + len, buf.length, buf, 0);
/*
hb = buf;
offset = 0;
*/
}
protected HeapByteBuffer(byte[] buf,
int mark, int pos, int lim, int cap,
int off)
{
super(mark, pos, lim, cap, buf, off);
/*
hb = buf;
offset = off;
*/
}
此时我们就得到了一个带有Capacity大小缓冲区的ByteBuffer对象,下面开始往缓冲区写数据,以int类型数据为列子。来分析一下putInt(int i)的源码。putInt()的实现是在HeapByteBuffer类中,通过调用了Bits的静态函数putInt完成的,其中put之后pos的移动是通过nextPutIndex()函数完成,Int大小4个字节,向后移动4个,该函数实在Buffer基类中实现的。bigEndian是一个bool变量,用来表示当前是大端存储还是小端存储,默认大端。
public ByteBuffer putInt(int x) {
Bits.putInt(this, ix(nextPutIndex(4)), x, bigEndian);
return this;
}
protected int ix(int i) {
return i + offset;//加上位置偏移
}
final int nextPutIndex(int nb) { // package-private
if (limit - position < nb)
throw new BufferOverflowException();
int p = position;
position += nb;//Pos指针后移
return p;//原始Pos指针返回,用来计算此次取出的数据
}
下面看Bits的put函数:
static void putInt(ByteBuffer bb, int bi, int x, boolean bigEndian) {
if (bigEndian)//根据不同的存储方式调用不同的解析函数
putIntB(bb, bi, x);
else
putIntL(bb, bi, x);
}
//以大端为例,这里主要是后面的intX()函数,用来对x进行位运算,取出相应位置的数据,放入到缓冲区的相应位置
static void putIntB(ByteBuffer bb, int bi, int x) {
bb._put(bi , int3(x));
bb._put(bi + 1, int2(x));
bb._put(bi + 2, int1(x));
bb._put(bi + 3, int0(x));
}
private static byte int3(int x) { return (byte)(x >> 24); }
private static byte int2(int x) { return (byte)(x >> 16); }
private static byte int1(int x) { return (byte)(x >> 8); }
private static byte int0(int x) { return (byte)(x ); }
到此位置,数据被放入到了缓冲区中,下面开始读取。读取之前一定要先调用flip()函数,该函数可以控制pos和limit的值,使得缓冲区可以在读写之间很好的切换,它的实现实在Buffer基类中,主要工作就是,limit转换成当前缓冲区在最后一次写入数据后的位置,pos和mark重置,从头开始读取数据,这就是为什么,在写入之后调用flip()函数在写入不但会覆盖之前写入的值,还会导致pos位置发生变化,不能从最开始读取数据。
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
下面看一下get函数,get函数的实现也是在子类HeapByteBuffer中,nextGetIndex函数实在鸡肋Buffer中实现的,主要功能就是get之后的pos后移工作。Bits.getInt和前面的Bits.putInt相似,不错过多介绍。
public int getInt() {
return Bits.getInt(this, ix(nextGetIndex(4)), bigEndian);
}
final int nextGetIndex(int nb) { // package-private
if (limit - position < nb)
throw new BufferUnderflowException();
int p = position;
position += nb;
return p;
}
至此 讲的差不多了。一些函数开头的判断没有详细的去讲,他们的主要工作就是在put和get的时候越界的异常抛出。
在看源码的时候发现了另一个函数,这个函数很有意思public int getInt(int i)
从字面上看上去好像是获取第i个Int,调用一下试试,看看疗效。
public int getInt(int i) {
return Bits.getInt(this, ix(checkIndex(i, 4)), bigEndian);
}
static public void asIntBuffer() {
ByteBuffer bBuf = ByteBuffer.allocate(512);
bBuf.putInt(1);
bBuf.putInt(2);
bBuf.putInt(3);
bBuf.putInt(4);
bBuf.putInt(5);
bBuf.putInt(6);
bBuf.putInt(7);
bBuf.flip();
System.out.println("缓冲区Pos:" + bBuf.position() + " 缓冲区Limit:"
+ bBuf.limit());
for (int i = 0; i < 7; i++) {
System.out.println(bBuf.getInt(i));
}
}
对应输出:
缓冲区Pos:0 缓冲区Limit:28
1
256
65536
16777216
2
512
131072
这时候机会发现,他并没有像我们想想的那样去工作,其中256,65536是怎么来的呢。继续看public int getInt(int i)
的源码。发现它和之前分getInt唯一不同的就是在checkIndex(4)
通过看 final int checkIndex(int i, int nb)
的源码发现,该函数什么都没做只是check了一下limit。那256优势怎么来的呢?
final int checkIndex(int i, int nb) { // package-private
if ((i < 0) || (nb > limit - i))
throw new IndexOutOfBoundsException();
return i;
}
下面开一下Bits.getInt()和 getIntB()以及makeInt()
的源码,我们能够知道,当我们要获取第i个位置的int时,也就是bi。此时bi并没有跳过4个字节,而是在Buffer数组总按照我们提供的i去取了i之后的三个字节,在加上第i个构成了一个4字节的int。
static int getInt(ByteBuffer bb, int bi, boolean bigEndian) {
return bigEndian ? getIntB(bb, bi) : getIntL(bb, bi) ;
}
static int getIntB(ByteBuffer bb, int bi) {
return makeInt(bb._get(bi ),
bb._get(bi + 1),
bb._get(bi + 2),
bb._get(bi + 3));
}
static private int makeInt(byte b3, byte b2, byte b1, byte b0) {
return (((b3 ) << 24) |
((b2 & 0xff) << 16) |
((b1 & 0xff) << 8) |
((b0 & 0xff) ));
}
至于256怎么来的?下面分析一下,Buffer是一个字节数组。当我们putInt(1)
和 putInt(2)
之后,里面的前8个字节数组是这个样子的(大端存储)
16进制
00 00 00 01 00 00 00 02
转成2进制
00000000 00000000 00000000 00000001 00000000 00000000 00000000 00000002
当我们取调用getInt(2)
时其实取出来的是包括第二个字节以及后面的三个,也就是00000000 00000000 00000001 00000000
也就是256, 后面的数字同理。
当我们知道getInt(i)
后我们在来看一下putInt(index,i);
看似是在第index位置插入Int值i,其实不然
static public void asIntBuffer() {
ByteBuffer bBuf = ByteBuffer.allocate(512);
for (int i = 0; i < 10; i++) {
bBuf.putInt(i, i);
}
System.out.println("缓冲区Pos:" + bBuf.position() + " 缓冲区Limit:"
+ bBuf.limit());
}
输出:缓冲区Pos:0 缓冲区Limit:512
我们发现,pos压根没有移动,Buffer中压根没数据。同getInt(i)
类似pputInt(inex,i)
同样没引起pos的移动,pos始终处于0的位置,在我们get数据时,在nextGetIndex()
函数校验时就抛出异常了,总上,使用putInt(index,i)
必须在index位置有数据的情况下使用。
final int nextGetIndex(int nb) { // package-private
if (limit - position < nb)
throw new BufferUnderflowException();
int p = position;
position += nb;
return p;
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/186644.html原文链接:https://javaforall.cn