ByteBuffer的Put和Get的用法和要注意的细节
来源:互联网 发布:天天生鲜项目源码 编辑:程序博客网 时间:2024/06/02 01:15
最近再看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:2834567
从上面的输出发现当flip()被调用之后如果在网buffer里面put数据会覆盖之前写入的数据,导致Position位置后移,如果在加一句get()就会出现java.nio.BufferUnderflowException异常,见下面的输出。
缓冲区Pos:8 缓冲区Limit:2834567Exception 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:28125665536167772162512131072
这时候机会发现,他并没有像我们想想的那样去工作,其中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; }
- ByteBuffer的Put和Get的用法和要注意的细节
- pay 和 put的用法
- HashMap的put和get方法原理
- hashmap的数据结构以及put和get
- Java String.split()的用法和注意细节
- POST和GET的区别以及PUT和DELETE
- ByteBuffer的allocate和allocateDirect
- ByteBuffer的allocate和allocateDirect
- ByteBuffer的allocate和allocateDirect
- ByteBuffer的allocate和allocateDirect
- ByteBuffer的allocate和allocateDirect
- byteBuffer的理解和使用
- byteBuffer的用法
- ByteBuffer的用法
- Http服务器的交互请求 GET,POST,PUT和DELETE
- Java HashMap的数据结构以及put和get方法
- url 的httppost 和http get ,put,delect
- GET,POST,PUT,DELETE的区别和联系
- 人工智能动态整理
- BFS_1
- C++STL之set容器
- 文本复制后自动实现跳转的js代码
- strncpy函数学习
- ByteBuffer的Put和Get的用法和要注意的细节
- block(块级元素)和 inline(内联元素) 的区别
- TestFlight,app正式上线之前的测试环节。
- java中的try-catch-finally的处理流程介绍
- ACM模板——最长公共子序列 LCS
- matlab中dist函数使用方法
- HDU 1075 字典树 树搜索 逆路径输出单词
- Get请求Demo
- 数据结构实验之链表一:顺序建立链表