NIO直接缓冲区与非直接缓冲区的区别及运行效率比较

字节缓冲区分为直接字节缓冲区与非直接字节缓冲区 。
如果字节缓冲区为直接字节缓冲区, 则 JVM 会尽量在直接字节缓 冲区上执行本机 I/O操作,也就是直接对内核空 间进行访问,以提高运行效率 。 提高运行效率的原理就是在每次调用基于操作系统的 1/0 操作之前或之后, JVM 都会尽量避免将缓冲区的内容复制到中间缓冲区中,或者从中间缓冲区中复制内容,这样就节省了一个步骤 。
工厂方法 allocateDirect()可以创建直接字节缓冲区,通过工厂方法 allocateDirect()返回的缓冲区进行内存的分配和释放所需的时间成本通常要高于非直接缓冲区。直接缓冲区操作的数据不在 JVM 堆中 , 而是在内核空间中,根据这个结构可以分析出 ,直接缓 冲区善于保存那些易受操作系统本机 1/0 操作影响的大量、 长时间保存的数据 。 

allocateDirect(int capacity)方法的作用:分配新的直接字节缓冲区。新缓冲区的位置将为零 ,其界限将为其容量 , 其标记是不确定的 。 无论它是否具有底层实现数组,其标记都是不确定的 。
allocate(int capacity)方法的作用 : 分配一个新的非直接字节缓冲区 。 新缓冲区的位置为零 ,其界限将为其容量,其标记是不确定的 。 它将具有一个底层实现数组,且其数组偏移量将为零。
 

直接缓冲区会直接作用于本地操作系统的 I/O ,处理数据的效率相比非直接缓冲区会快一些

使用非直接缓冲区的测试代码如下
 

    public static void testAllocate(){
        long beginTime = System.currentTimeMillis();
        ByteBuffer allocate = ByteBuffer.allocate(190000000);
        for (int i = 0 ; i < 190000000 ; i++){
            allocate.put((byte) 123);
        }
        long endTime = System.currentTimeMillis();
        System.out.print("非直接缓冲区:"+(endTime - beginTime));
    }

使用直接缓冲区源代码如下 :
 

    public static void testAllocateDirect(){
        long beginTime = System.currentTimeMillis();
        ByteBuffer allocate = ByteBuffer.allocateDirect(190000000);
        for (int i = 0 ; i < 190000000 ; i++){
            allocate.put((byte) 123);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("直接缓冲区:"+(endTime - beginTime));
    }

结果如下:

从运行结果来看,直接缓冲区比非直接缓冲区在运行效率上要高一些,是什么原因造成这样的结果呢?直接缓冲区是使用 DirectByteBuffer 类进行实现的,而非直接缓冲区是使用 HeapByteBuffer 类进行实现的 。 直接缓冲区的实现类 DirectByteBuffer 的 put(byte)方法的源代码如下:

public ByteBuffer put( byte x) {
	unsafe .putByte(ix(nextPutindex()) , ((x))) ;
    return this ;
}

直接缓冲区( DirectByteBuffer)在内部使用 sun.misc.Unsafe 类进行值的处理。 Unsafe类的作用是 JVM 与操作系统进行直接通信,提高程序运行的效率,但正如其类的名称Unsafe 一样,该类在使用上并不是安全的,如果程序员使用不当,那么极有可能出现处理数据上的错误,因此,该类并没有公开化( public ),仅由 JDK 内部使用 。
而非直接缓冲区的实现类 HeapByteBuffer 的 put(byte)方法的源代码如下:

public ByteBuffer put( byte x) {
    hb[ix(nextPutindex())] = x ;
    return this;
}

非直接缓冲区( HeapByteBuffer)在内部直接对 byte口 hb 字节数组进行操作,而且还是在 JVM 的堆中进行数据处理,因此运行效率相对慢一些 。