Java Nio Buffer 四

2018/01/03 Java

Java Nio Buffer 四

这次主要内容是直接缓冲区和视图缓冲区。

直接缓冲区

字节缓冲区跟其他缓冲区类型最明显的不同在于,它们可以成为通道所执行的 I/O 的源头和/或目标。操作系统的在内存区域中进行 I/O 操作。这些内存区域,就操作系统方面而言,是相连的字节序列。毫无疑问,只有字节缓冲区有资格参与 I/O 操作。这也意味着 I/O 操作的目标内存区域必须是连续的字节序列。在 JVM 中,字节数组可能不会在内存中连续存储,或者无用存储单元收集可能随时对其进行移动。在 Java 中,数组是对象,而数据存储在对象中的方式在不同的 JVM 实现中都各有不同。 出于这一原因,引入了直接缓冲区的概念。直接缓冲区被用于与通道和固有 I/O 例程交互。它们通过使用固有代码来告知操作系统直接释放或填充内存区域,对用于通道直接或原始存取的内存区域中的字节元素的存储尽了最大的努力。 直接字节缓冲区通常是 I/O 操作最好的选择。在设计方面,它们支持 JVM 可用的最高效 I/O 机制。非直接字节缓冲区可以被传递给通道,但是这样可能导致性能损耗。通常非直接缓冲不可能成为一个本地 I/O 操作的目标。如果您向一个通道中传递一个非直接 ByteBuffer 对象用于写入,通道可能会在每次调用中隐含地进行下面的操作:

  1. 创建一个临时的直接 ByteBuffer 对象。
  2. 将非直接缓冲区的内容复制到临时缓冲中。
  3. 使用临时缓冲区执行低层次 I/O 操作。
  4. 临时缓冲区对象离开作用域,并最终成为被回收的无用数据。 这可能导致缓冲区在每个 I/O 上复制并产生大量对象,而这种事都是我们极力避免的。 不过,依靠工具,事情可以不这么糟糕。运行时间可能会缓存并重新使用直接缓冲区或者执行其他一些聪明的技巧来提高吞吐量。如果您仅仅为一次使用而创建了一个缓冲区,区别并不是很明显。另一方面,如果您将在一段高性能脚本中重复使用缓冲区,分配直接缓冲区并重新使用它们会更加高效。 直接缓冲区时 I/O 的最佳选择,但可能比创建非直接缓冲区要花费更高的成本。直接缓冲区使用的内存是通过调用本地操作系统方面的代码分配的,绕过了标准 JVM 堆栈。建立和销毁直接缓冲区会明显比具有堆栈的缓冲区更加消耗时间,这取决于主操作系统以及 JVM 实现。 直接缓冲区的内存区域不受GC控制,因为它们位于标准 JVM 堆栈之外。 直接 ByteBuffer 是通过调用具有所需容量的 ByteBuffer.allocateDirect()函数 产生的,就像我们之前所涉及的 allocate()函数一样。注意用一个 wrap()函数所创建的被 包装的缓冲区总是非直接的。 部分代码如下:
    public abstract class ByteBuffer
         extends Buffer implements Comparable{
         // This is a partial API listing
         public static ByteBuffer allocate (int capacity) 
         public static ByteBuffer allocateDirect (int capacity) 
         public abstract boolean isDirect( );
    }
    

    所有的缓冲区都提供了一个叫做 isDirect()的 boolean 函数,来测试特定缓冲区是否 为直接缓冲区。虽然 ByteBuffer 是唯一可以被直接分配的类型,但如果基础缓冲区是一个 Direct ByteBuffer,对于非字节视图缓冲区,isDirect()可以是 true。

“我们应该忘记小的效率,因为97%的情况下:过早的优化是所有祸害的根源。”——唐纳 德·克努特。

视图缓冲区

I/O 基本上可以归结成组字节数据的四处传递。在进行大数据量的 I/O 操作时,很又可能您会使用各种 ByteBuffer 类去读取文件内容,接收来自网络连接的数据,等等。一旦数据到达了 ByteBuffer,就需要查看它以决定怎么做或者在将它发送出去之前对它进行一些操作。ByteBuffer 类提供了丰富的 API 来创建视图缓冲区。 视图缓冲区通过已存在的缓冲区对象实例的工厂方法来创建。这种视图对象维护它自己的属性,容量,位置,上界和标记,但是和原来的缓冲区共享数据元素。但是 ByteBuffer 类允许创建视图来将 byte 型缓冲区字节数据映射为其它的原始数据类型。例如,asLongBuffer()函数创建一个将八个字节型数据当成一个 long 型数据来存取的视图缓冲区。 函数声明如下:

public abstract class ByteBuffer
          extends Buffer implements Comparable{
          // This is a partial API listing
        public abstract CharBuffer asCharBuffer( ); 
        public abstract ShortBuffer asShortBuffer( ); 
        public abstract IntBuffer asIntBuffer( ); 
        public abstract LongBuffer asLongBuffer( ); 
        public abstract FloatBuffer asFloatBuffer( ); 
        public abstract DoubleBuffer asDoubleBuffer( );
}

新的缓冲区的容量是字节缓冲区中存在的元素数量除以视图类型 中组成一个数据类型的字节数。在切分中任一个超过上界的元素对于这个视图 缓冲区都是不可见的。视图缓冲区的第一个元素从创建它的 ByteBuffer 对象的位置开始 (positon()函数的返回值)。具有能被自然数整除的数据元素个数的视图缓冲区是一种较好的实现。

如下代码片段:

ByteBuffer byteBuffer =
    ByteBuffer.allocate (7).order (ByteOrder.BIG_ENDIAN);
CharBuffer charBuffer = byteBuffer.asCharBuffer( );

如图所示: 图一

当直接从 byte 型缓冲区中采集数据时,视图缓冲区可能会提高效率。如果这个视图的字节顺序和本地机器硬件的字节顺序一致,低级的(相对于高级语言而言)语言的代码可以直接存取缓冲区中的数据值,而不是通过比特数据的包装和解包装过程来完成。

Show Disqus Comments

Search

    Table of Contents