Buffer类的详解(转)

本文详细介绍了Java NIO中的Buffer类,包括Buffer的四个关键属性:容量、上界、位置和标记,以及如何使用Buffer进行数据读写。文章还讲解了Buffer的翻转、释放、清除、压缩等操作,以及批量移动数据的方法。此外,还探讨了如何创建和复制Buffer,特别是ByteBuffer的特殊性,如字节顺序和直接缓冲区。最后,提到了视图缓冲区的概念和使用。
摘要由CSDN通过智能技术生成

这篇蚊帐转自:http://zachary-guo.iteye.com/blog/1457542,作者写的非常好,是我看到的写nio最好的几篇蚊帐,但原文中有一些错误,还有我自己对这方面的一些理解,在这里一并更改了。

Buffer 类是 java.nio 的构造基础。一个 Buffer 对象是固定数量的数据的容器,其作用是一个存储器,或者分段运输区,在这里,数据可被存储并在之后用于检索。缓冲区可以被写满或释放。对于每个非布尔原始数据类型都有一个缓冲区类,即 Buffer 的子类有:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer 和 ShortBuffer,是没有 BooleanBuffer 之说的。尽管缓冲区作用于它们存储的原始数据类型,但缓冲区十分倾向于处理字节。非字节缓冲区可以在后台执行从字节或到字节的转换,这取决于缓冲区是如何创建的。
◇ 缓冲区的四个属性
        所有的缓冲区都具有四个属性来提供关于其所包含的数据元素的信息,这四个属性尽管简单,但其至关重要,需熟记于心:

  • 容量(Capacity):缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变。
  • 上界(Limit):缓冲区的第一个不能被读或写的元素。缓冲创建时,limit 的值等于 capacity 的值。假设 capacity = 1024,我们在程序中设置了 limit = 512,说明,Buffer 的容量为 1024,但是从 512 之后既不能读也不能写,因此可以理解成,Buffer 的实际可用大小为 512。
  • 位置(Position):下一个要被读或写的元素的索引。位置会自动由相应的 get() 和 put() 函数更新。 这里需要注意的是positon的位置是从0开始的。
  • 标记(Mark):一个备忘位置。标记在设定前是未定义的(undefined)。使用场景是,假设缓冲区中有 10 个元素,position 目前的位置为 2(也就是如果get的话是第三个元素),现在只想发送 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 为例,当然,它当然有 get() 和 put() 方法了。
◇ 相对存取和绝对存取

Java代码

  1. public abstract class ByteBuffer extends Buffer implements Comparable { 
  2. // This is a partial API listing
  3. public abstract byte get( );  
  4. public abstract byte get (int index);  
  5. public abstract ByteBuffer put (byte b);  
  6. public abstract ByteBuffer put (int index, byte b); 

        来看看上面的代码,有不带索引参数的方法和带索引参数的方法。不带索引的 get 和 put,这些调用执行完后,position 的值会自动前进。当然,对于 put,如果调用多次导致位置超出上界(注意,是 limit 而不是 capacity),则会抛出 BufferOverflowException 异常;对于 get,如果位置不小于上界(同样是 limit 而不是 capacity),则会抛出 BufferUnderflowException 异常。这种不带索引参数的方法,称为相对存取,相对存取会自动影响缓冲区的位置属性。带索引参数的方法,称为绝对存取,绝对存储不会影响缓冲区的位置属性,但如果你提供的索引值超出范围(负数或不小于上界),也将抛出 IndexOutOfBoundsException 异常。
◇ 翻转
        我们把 hello 这个串通过 put 存入一 ByteBuffer 中,如下所示:将 hello 存入 ByteBuffer 中

Java代码

  1. ByteBuffer buffer = ByteBuffer.allocate(1024); 
  2. buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o'); 

        此时,position = 5,limit = capacity = 1024。现在我们要从正确的位置从 buffer 读数据,我们可以把 position 置为 0,那么字符串的结束位置在哪呢?这里上界该出场了。如果把上界设置成当前 position 的位置,即 5,那么 limit 就是结束的位置。上界属性指明了缓冲区有效内容的末端。人工实现翻转:

Java代码

  1. buffer.limit(buffer.position()).position(0); 

        但这种从填充到释放状态的缓冲区翻转是API设计者预先设计好的,他们为我们提供了一个非常便利的函数:buffer.flip()。另外,rewind() 函数与 flip() 相似,但不影响上界属性,它只是将位置值设回 0。在进行buffer读操作的时候,一般都会使用buffer.flip()函数。 
◇ 释放(Drain)
        这里的释放,指的是缓冲区通过 put 填充数据后,然后被读出的过程。上面讲了,要读数据,首先得翻转。那么怎么读呢?hasRemaining() 会在释放缓冲区时告诉你是否已经达到缓冲区的上界:hasRemaining()函数和Remaining()函数有密切的功能,

Java代码

  1. for (int i = 0; buffer.hasRemaining(); i++) { 
  2.     myByteArray[i] = buffer.get(); 

        很明显,上面的代码,每次都要判断元素是否到达上界。我们可以做:改变后的释放过程

Java代码

  1. int count = buffer.hasRemaining(); 
  2. for (int i = 0; i < count; i++) { 
  3.     myByteArray[i] = buffer.get(); 

        第二段代码看起来很高效,但请注意,缓冲区并不是多线程安全的。如果你想以多线程同时存取特定的缓冲区,你需要在存取缓冲区之前进行同步。因此,使用第二段代码的前提是,你对缓冲区有专门的控制。
◇ buffer.clear()
        clear() 函数将缓冲区重置为空状态。它并不改变缓冲区中的任何数据元素,而是仅仅将 limit 设为容量的值,并把 position 设回 0。
◇ Compact(不知咋翻译,压缩?紧凑?)
        有时候,我们只想释放出一部分数据,即只读取部分数据。当然,你可以把 postion 指向你要读取的第一个数据的位置,将 limit 设置成最后一个元素的位置 + 1。但是,一旦缓冲区对象完成填充并释放,它就可以被重新使用了。所以,缓冲区一旦被读取出来,已经没有使用价值了。
        以 Mellow 为例,填充后为 Mellow,但如果我们仅仅想读取 llow。读取完后,缓冲区就可以重新使用了。Me 这两个位置对于我们而言是没用的。我们可以将 l

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值