黑马程序员_O‘Reilly java nio学习笔记之缓冲区2

---------------------- android培训java培训、期待与您交流! ----------------------


2.9   比较

     Buffer的子类有equals和compareTo两个比较方法。

     Equals比较时,两个缓冲区被认为相等的充要条件是: 

l 两个对象类型相同。包含不同数据类型的 buffer 永远不会相等。

l 两个对象都剩余同样数量的元素。Buffer 的容量不需要相同,而且缓冲区中剩余数据的索引也不必相同。但每个缓冲区中剩余元素的数目(从位置到上界)必须相同。 

l 在每个缓冲区中应被Get()函数返回的剩余数据元素序列必须一致。 

     如果不满足以上任意条件,就会返回 false。 

图 2-10 说明了两个属性不同的缓冲区也可以相等。 

图 2-11 显示了两个相似的缓冲区,可能看起来是完全相同,但测试时会发现并不相等。 



     compareTo()函数以词典顺序进行比较,返回1,0,或-1。a.compareTo(b)返回值规则:a-b>0,返回1。与 equals()相似,compareTo()不允许不同对象间进行比较。但compareTo()更为严格:如

果您传递一个类型错误的对象,它会抛出ClassCastException异常,但equals()只会返回false。

     比较是针对每个缓冲区内剩余数据进行的,与它们在 equals(  )中的方式相同,直到不相等

的元素被发现或者到达缓冲区的上界。如果一个缓冲区在不相等元素发现前已经被耗尽,较短的缓冲区被认为是小于较长的缓冲区。不像equals(),compareTo()不可交换。

2.10  批量移动

在Buffer的子类中,定义了用于得到批量数据的方法,如:

ByteBuffer get(byte[] dst)                                   相对批量 get 方法。 

ByteBuffer get(byte[] dst, int offset, int length)           相对批量 get 方法。 

批量移动总是具有指定的长度。否则,可能抛出异常。如:

byte[] x=new byte[20];

ByteBuffer a=ByteBuffer.allocate(10);

a.get(x);

以上代码抛出java.nio.BufferUnderflowException。正确的读取数据方式为:

byte[] x=new byte[20];

ByteBuffer a=ByteBuffer.allocate(10);

a.put((byte)33);

a.flip();

int len=a.remaining();

    a.get(x,0,len);

    System.out.println(new String(x));

如果缓冲区存有比数组能容纳的数量更多的数据,您可以重复利用如下文所示的程序块进行读取:  

char [] smallArray = new char [10];  

while (buffer.hasRemaining(  )) {  

        int length = Math.min (buffer.remaining(  ), smallArray.length);  

        buffer.get (smallArray, 0, length);  

       doWith(smallArray );//处理数据

 

     put的批量方式与get相似。 buffer.put(myArray); 等价于: 

buffer.put(myArray,0,myArray.length); 

     如果缓冲区有足够的空间接受数组中的数据(buffer.remaining()>myArray.length),数据将会被复制到从当前位置开始的缓冲区,并且缓冲区位置会增加数据元素的数量。如果缓冲区中没有足够的空间,那么不会有数据被传递,同时抛出一个BufferOverflowException 异常。 

     也可以通过调用带有一个缓冲区引用作为参数的 put()来在两个缓冲区内进行批量传递。 

buffer.put(srcBuffer); 这等价于(假设dstBuffer有足够的空间): 

          while (srcBuffer.hasRemaining(  )) {  

               dstBuffer.put (srcBuffer.get(  ));  

          } 

两个缓冲区的位置都会前进所传递的数据元素的数量。范围检查会像对数组一样进行。具体来说,如果 srcBuffer.remaining()大于 dstBuffer.remaining(),那么数据不会被传递,同时抛出 BufferOverflowException 异常。如果您对将一个缓冲区传递给它自己,就会引发java.lang.IllegalArgumentException异常。

  对于CharBuffer,还有一些特定于String的操作方法。如:

CharBuffer put(String src)和CharBuffer put(String src, int start, int end)。

要将myString 的5-8个字符复制到buffer,应为:buffer.put(myString,5,9);

     String批量移动等效于下面的代码: 

for (int i = start; i < end; i++) }  

        buffer.put (myString.charAt (i)); 

对 String 要进行与 char 数组相同的范围检查。如果所有的字符都不适合缓冲区,将会抛出BufferOverflowException异常。 

3.创建缓冲区

     分配操作创建一个缓冲区对象并分配一个私有的空间来储存容量大小的数据元素。包装操作创建一个缓冲区对象但是不分配任何空间来储存数据元素。它使用您所提供的数组作为存储空间来储存缓冲区中的数据元素。 分配一个容量为100 个char变量的 Charbuffer: 

         CharBuffer charBuffer = CharBuffer.allocate (100); 

这段代码隐含地从堆空间中分配了一个 char 型数组作为备份存储器来储存 100 个 char

变量。 

如果您想提供您自己的数组用做缓冲区的备份存储器,请调用wrap()函数: 

static CharBuffer wrap(char[] array)   将字符数组包装到缓冲区中。 

static CharBuffer wrap(char[] array, int offset, int length)  将字符数组包装到缓冲区中。  

示例:

char [] myArray = new char [100]; 

CharBuffer charbuffer = CharBuffer.wrap (myArray); 

这段代码构造了一个新的缓冲区对象,但数据元素会存在于数组中。这意味着通过调用put()函数造成的对缓冲区的改动会直接影响这个数组,而且对这个数组的任何改动也会对这个缓冲区对象可见。带有 offset 和 length 作为参数的 wrap()函数版本则会构造一个按照您提供的offset和 length 参数值初始化位置和上界的缓冲区。这样做: 

   CharBuffer charbuffer = CharBuffer.wrap (myArray, 12, 42); 

创建了一个 position 值为 12,limit 值为 54,容量为 myArray.length 的缓冲区。若offset+length>myArray.length,抛出java.lang.IndexOutOfBoundsException异常。offset 和 length 参数只是设置了初始的状态。调用使用上面代码中的方法创建的缓冲区中的 clear()函数,然后对其进行填充,直到超过上界值,这将会重写数组中的所有元素。

CharBuffer创建缓冲区异于Buffer其它子类的方法有:

static CharBuffer wrap(CharSequence csq) 

static CharBuffer wrap(CharSequence csq, int start, int end) 

实现了CharSequence 接口的方法有CharBuffer,String, StringBuffer, StringBuilder 。

以上方法将字符序列包装到只读的缓冲区中。 新的只读缓冲区的内容将为给定字符序列的内容。新缓冲区的容量和界限将为 csq.length(),其位置将为零,其标记是未定义的。 三个参数的 wrap()函数版本使用 start 和 end 下标参数来描述传入的 CharSequence对象的子序列

   

4.复制缓冲区

     缓冲区不限于管理数组中的外部数据。它们也能管理其他缓冲区中的外部数据。当一个管

理其他缓冲器所包含的数据元素的缓冲器被创建时,这个缓冲器被称为视图缓冲器。大多数的

视图缓冲器都是 ByteBuffer的视图。在继续前往字节缓冲器的细节之前,我们先将注意力放在所有存储器类型的共同视图上。视图存储器总是通过调用已存在的存储器实例中的函数来创建。我们将再次以 CharBuffer 为例,但同样的操作可被用于任何基本的缓冲区类型。创建视图的方法有:  

          public abstract CharBuffer duplicate();  

          public abstract CharBuffer asReadOnlyBuffer();  

          public abstract CharBuffer slice();  

     duplicate()创建缓冲区特征:新缓冲区的内容将为此缓冲区的内容。此缓冲区内容的更改在新缓冲区中是可见的,反之亦然;这两个缓冲区的位置、界限和标记值是相互独立的。 新缓冲区的容量、界限、位置和标记值将与此缓冲区相同。当且仅当此缓冲区为直接时,新缓冲区才是直接的,当且仅当此缓冲区为只读时,新缓冲区才是只读的。返回的是新的字符缓冲区。

     我们可以使用 asReadOnlyBuffer()函数来生成一个只读的缓冲区视图。这与duplicate()相同,除了这个新的缓冲区不允许使用 put(),并且其 isReadOnly()函数将会返回 true 。对这一只读缓冲区的 put() 函数的调用尝试会导致抛出ReadOnlyBufferException异常。 

:如果一个只读的缓冲区与一个可写的缓冲区共享数据,或者有包装好的备份数组,那么对这个可写的缓冲区或直接对这个数组的改变将反映在所有关联的缓冲区上,包括只读缓冲区。

     slice() 创建的新缓冲区的内容将从原缓冲区的当前位置开始。原缓冲区内容的更改在新缓冲区中是可见的,反之亦然;这两个缓冲区的位置、界限和标记值是相互独立的。 新缓冲区的位置将为零,其容量和界限将为此缓冲区中所剩余的字符数量,其标记是未定义的。当且仅当原缓冲区为直接时,新缓冲区才是直接的,当且仅当原缓冲区为只读时,新缓冲区才是只读的。 

例:

    要创建一个映射到数组位置 12-20(9 个元素)的 buffer 对象,应使用下面的代码实现: 

char [] myBuffer = new char [100];  

CharBuffer cb = CharBuffer.wrap (myBuffer);  

cb.position(12).limit(21);  

CharBuffer sliced = cb.slice(); 

注:不能用wrap(char[] array, int offset, int length)创建。

5.字节缓冲区

5.1   字节顺序 

    关于ByteOrder 类、缓冲区字节顺序、JVM字节顺序等的讲解,详见书籍。

5.2   直接缓冲区

    字节缓冲区跟其他缓冲区类型最明显的不同在于,它们可以成为通道所执行的 I/O 的源

头和/或目标。通道只接收 ByteBuffer 作为参数。 

    直接字节缓冲区通常是 I/O 操作最好的选择,但可能比创建非直接缓冲区要花费更高的成本。直接缓冲区使用的内存是通过调用本地操作系统方面的代码分配的,绕过了标准 JVM 堆栈。建立和

销毁直接缓冲区会明显比具有堆栈的缓冲区更加破费,这取决于主操作系统以及 JVM 实现。在设计方面,它们支持 JVM 可用的最高效I/O 机制。非直接字节缓冲区可以被传递给通道,但是这样可能导致性能损耗。关于直接缓冲区详细情况,详见教程。

直接 ByteBuffer 是通过调用具有所需容量的 ByteBuffer.allocateDirect()函数产生的。注意用一个 wrap()函数所创建的被包装的缓冲区总是非直接的。所有的缓冲区都提供了一个叫做 isDirect()的 boolean 函数,来测试特定缓冲区是否为直接缓冲区。虽然 ByteBuffer 是唯一可以创建直接缓冲区的类,但是,其他缓冲区可以通过视图的方式得到其直接缓冲区(后面讲解)。

5.3   视图缓冲区

     ByteBuffer 类提供了丰富的 API 来创建视图缓冲区,它允许创建视图来将 byte 型缓冲区字节数据映射为其它的原始数据类型。如:

  public abstract CharBuffer asCharBuffer(  );  

  public abstract ShortBuffer asShortBuffer(  );  

  public abstract IntBuffer asIntBuffer(  );  

  public abstract LongBuffer asLongBuffer(  );  

  public abstract FloatBuffer asFloatBuffer(  );  

  public abstract DoubleBuffer asDoubleBuffer(  ); 

     新缓冲区的内容将从此缓冲区的当前位置开始。此缓冲区内容的更改在新缓冲区中是可见的,反之亦然;这两个缓冲区的位置、界限和标记值是相互独立的。新缓冲区的位置将为零,其容量和界限将为此缓冲区中所剩余的字节数的nn基本类型字节数)分之一,其标记是不确定的。当且仅当此缓冲区为直接时,新缓冲区才是直接的,当且仅当此缓冲区为只读时,新缓冲区才是只读的。

示例:

public class BufferCharView {

public static void main(String[] argv) throws Exception {

ByteBuffer byteBuffer = ByteBuffer.allocate(7);

CharBuffer charBuffer1 = byteBuffer.asCharBuffer();

byteBuffer.put(0, (byte) 0);

byteBuffer.put(1, (byte'H');

byteBuffer.put(2, (byte) 0);

byteBuffer.put(3, (byte'i');

byteBuffer.put(4, (byte) 0);

byteBuffer.put(5, (byte'!');

byteBuffer.put(6, (byte)0);

        byteBuffer.position(2);

        

        CharBuffer charBuffer2 = byteBuffer.asCharBuffer();

        println(byteBuffer);

println(charBuffer1);

println(charBuffer2);

}

private static void println(Buffer buffer) {

System.out.println("pos=" + buffer.position() + ", limit="

+ buffer.limit() + ", capacity=" + buffer.capacity()

": buffer.toString='" + buffer.toString() + "'");

}

}输出:

pos=2, limit=7, capacity=7: 

             buffer.toString='java.nio.HeapByteBuffer[pos=2 lim=7 cap=7]'

pos=0, limit=3, capacity=3: buffer.toString='Hi!'

pos=0, limit=2, capacity=2: buffer.toString='i!'

最后单独一个字节舍弃。

     一旦您得到了视图缓冲区,您可以用 duplicate() , slice() 和asReadOnlyBuffer()函数创建进一步的子视图。无论何时一个视图缓冲区存取一个 ByteBuffer 的基础字节,这些字节都会根据这个视图缓冲区的字节顺序设定被包装成一个数据元素。因此,大端和小端顺序的ByteBuffer得到的视图是不同的。当一个视图缓冲区被创建时,视图创建的同时它也继承了基础 ByteBuffer 对象的字节顺序设定。这个视图的字节排序不能再被修改。

5.4    数据元素视图 

    ByteBuffer 类提供了一个不太重要的机制来以多字节数据类型的形式存取 byte 数据

组。ByteBuffer 类为每一种原始数据类型提供了存取的和转化的方法: 

        public abstract char getChar(  );  

        public abstract char getChar (int index);  

        public abstract short getShort(  );  

        public abstract short getShort (int index);  

        public abstract int getInt(  );  

        public abstract int getInt (int index);  

        public abstract long getLong(  );  

        public abstract long getLong (int index);  

        public abstract float getFloat(  );  

        public abstract float getFloat (int index);  

        public abstract double getDouble(  );  

        public abstract double getDouble (int index);  

        public abstract ByteBuffer putChar (char value);  

        public abstract ByteBuffer putChar (int index, char value);  

        public abstract ByteBuffer putShort (short value);  

        public abstract ByteBuffer putShort (int index, short value);  

        public abstract ByteBuffer putInt (int value);  

        public abstract ByteBuffer putInt (int index, int value);  

        public abstract ByteBuffer putLong (long value);  

        public abstract ByteBuffer putLong (int index, long value);  

        public abstract ByteBuffer putFloat (float value);  

        public abstract ByteBuffer putFloat (int index, float value);  

        public abstract ByteBuffer putDouble (double value);  

        public abstract ByteBuffer putDouble (int index, double value);  

假设一个叫buffer 的ByteBuffer对象处于下图的状态:


这段代码: 

         int value = buffer.getInt( );

会返回一个由缓冲区中位置 1-4 的 byte 数据值组成的 int 型变量的值。实际的返回值取决于缓冲区的当前的比特排序(byte-order)设置。

更具体的写法是: 

         int value = buffer.order (ByteOrder.BIG_ENDIAN).getInt( ); 

这将会返回值0x3BC5315E,同时:  

         int value = buffer.order (ByteOrder.LITTLE_ENDIAN).getInt( ); 

返回值 0x5E31C53B。 (注意大端小端的概念)

     如果您试图获取的原始类型需要比缓冲区中存在的字节数更多的字节,会抛出

BufferUnderflowException。

     put 函数提供与 get 相反的操作。原始数据的值会根据字节顺序被分拆成一个个 byte数据。如果存储这些字节数据的空间不够,会抛出BufferOverflowException。 

5.5   存取无符号数据 

Java 编程语言对无符号数值并没有提供直接的支持(除了 char 类型)。当您必须处理缓冲区中的无符号数据时,可以参见教程。




---------------------- android培训java培训、期待与您交流! ----------------------详细请查看:http://edu.csdn.net/heima

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值