先来看看大牛们是怎么解释的:Buffer 类是 java.nio 的构造基础。一个 Buffer 对象是固定数量的数据的容器,其作用是一个存储器,或者分段运输区,在这里,数据可被存储并在之后用于检索。缓冲区可以被写满或释放。对于每个非布尔原始数据类型都有一个缓冲区类,即 Buffer 的子类有:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer 和 ShortBuffer,是没有 BooleanBuffer 之说的。尽管缓冲区作用于它们存储的原始数据类型,但缓冲区十分倾向于处理字节。非字节缓冲区可以在后台执行从字节或到字节的转换,这取决于缓冲区是如何创建的。 技术大牛描述的可能专业了点,我也不是很明白,我把其简洁的理解为,Buffer是来暂时存储和传送数据的载体,类似公交车似的。
关于Buffer的讲解网络上有很多,我也是站在巨人的肩膀上来做一个小小的总结。那么先来了解一下ByteBuffer的基本原理吧:
可以参考文章http://zachary-guo.iteye.com/blog/1457542
◇ 缓冲区的四个属性
所有的缓冲区都具有四个属性来提供关于其所包含的数据元素的信息,这四个属性尽管简单,但其至关重要,需熟记于心:
容量(Capacity):缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变。
上界(Limit):缓冲区的第一个不能被读或写的元素。缓冲创建时,limit 的值等于 capacity 的值。假设 capacity = 1024,我们在程序中设置了 limit = 512,说明,Buffer 的容量为 1024,但是从 512 之后既不能读也不能写,因此可以理解成,Buffer 的实际可用大小为 512。
位置(Position):下一个要被读或写的元素的索引。位置会自动由相应的 get() 和 put() 函数更新。
标记(Mark):一个备忘位置。标记在设定前是未定义的(undefined)。使用场景是,假设缓冲区中有 10 个元素,position 目前的位置为 2,现在只想发送 6 - 10 之间的缓冲数据,此时我们可以 buffer.mark(buffer.position()),即把当前的 position 记入 mark 中,然后 buffer.postion(6),此时发送给 channel 的数据就是 6 - 10 的数据。发送完后,我们可以调用 buffer.reset() 使得 position = mark,因此这里的 mark 只是用于临时记录一下位置用的。
请切记,在使用 Buffer 时,我们实际操作的就是这四个属性的值。我们发现,Buffer 类并没有包括 get() 或 put() 函数。但是,每一个Buffer 的子类都有这两个函数,但它们所采用的参数类型,以及它们返回的数据类型,对每个子类来说都是唯一的,所以它们不能在顶层 Buffer 类中被抽象地声明。它们的定义必须被特定类型的子类所遵从。下面我只总结一下ByteBuffer的简单用法。我主要通过一些实例来去真正理解其用法。
public abstract class ByteBuffer extends Buffer implements Comparable {
// This is a partial API listing
public abstract byte get( );
public abstract byte get (int index);
public abstract ByteBuffer put (byte b);
public abstract ByteBuffer put (int index, byte b);
}
//这里是创建ByteBuffer的实例,大小设为1024个int,即1024*4个字节
ByteBuffer buffer = ByteBuffer.allocate(1024);
如果想要往里面进行加入数据可以如下操作:
//此处写入"hey"
buffer.position();//0
buffer.put((byte)'H');//position=1
buffer.put((byte)'e');//position=2
buffer.put((byte)'y'); //position=3;
可以看出,这里我是一个字节一个字节的写入的,另外一种,可以一批量的取写数据。如下:
public abstract class ByteBuffer extends Buffer implements Comparable {
public ByteBuffer get(byte[] dst);
public ByteBuffer get(byte[] dst, int offset, int length);
public final ByteBuffer put(byte[] src);
public ByteBuffer put(byte[] src, int offset, int length);
}
同理,比如我要写入“hello world”
//这里是一起写入的,也可以定义一个固定长度的byte[]数组
String str="hello world";
byte[] buf=str.getBytes();
buffer.put(buf);
//buffer.put(buf,0,buf.length);
无论怎么写进去,在读出的时候,要按照写入的顺序读取。(先写先读)以 读取数组为例
buffer.flip();
//这里数组的大小应该和数据的长度一致
byte[] dc=new byte[buffer.limit()];
buffer.get(dc);
buffer.clear();
惊奇的是,这里又出现了limit();flip();clear();三个方法,先来用第一个例子来解释一下
//此处写入"hey"
//初始化ByteBuffer时(position,limit,capacity),(0,1024,1024)
buffer.position();//(0,1024,1024)
buffer.put((byte)'H');//(1,1024,1024)
buffer.put((byte)'e');//(2,1024,1024)
buffer.put((byte)'y'); //(3,1024,1024)
buffer.flip();//(0,3,1024),这里将position的值给了limit,也就相当于把有效的数据长度给了limit
buffer.clear();//(0,1024,1024),此时再次回到了初始化状态,注意,里面的数据并没有被清空
通过上面实例描述,相信大家应该能看懂。我讲的可能不是太详细,关于这两种方法大家可以在网上搜索一下,帮助加深理解。
另外,在ByteBuffer读数据时,我们还可能会遇到循环读取与写入数据。此时读取数据的循环条件通常用有remining()和hasRemining()这类似于枚举中的hasElement()方法。举个简单的例子:
我想把联系人的信息写入ByteBuffer内,因为联系人不可能只有一个,所以说,必须得循环的写入数据,上代码
//datas为存取联系人信息的集合,ContacstBean是实体类
for (int i = 0; i < datas.size(); i++) {
ContactsBean contact = datas.get(i);
// 电话号码,比如号码共占10个字节
buffer.put(contact.getNumber().getBytes());
// 联系人姓名,比如姓名占20个字节
buffer.put(contact.getName().getBytes());
}
下面来读取数据:
buffer.flip();
while(buffer.remining()>20){
byte[] number=new byte[10];
byte[] name=new byte[20];
buffer.get(number);
buffer.get(name);
}
buffer.clear();
上面条件buffer.remining()>20我来解释一下,因为buffer.remining()每次只读一个字节,当剩余的数据如果没有20个字节的时候会出现异常。
以上是我总结的关于ByteBuffer的一些简单用法,自我感觉,对其了解还只是存在于浅尝辄止的状态,深入理解还需要一些时间。
另外个人总结了关于ByteBuffer的读写遵循原则:先写先读,写多少读多少。