Java有几种文件拷贝方式?哪一种最高效? - 《java核心技术》笔记

java的几种文件拷贝方式?哪一种最高效?

java.io的FileInputStream/FileOutputStream或者java.nio的transferTo/transferFrom。
nio方式可能更快,因为使用了零拷贝技术,数据传输不需要切换到用户态参与,减少了上下文切换和不必要的内存拷贝。
b0c8226992bb97adda5ad84fe25372ea

比如应用读取数据时,先在内核态将数据从磁盘读取到内核缓存,再切换到用户态将数据从内核缓存读取到用户缓存。

而NIO transferTo则直接在内核中进行数据拷贝。

提高类似拷贝等IO操作的性能的原则

  • 使用缓存,减少IO次数;
  • 使用transferTo等机制,减少上下文切换和额外IO操作;
  • 减少不必要的转换,比如编解码。

NIO Buffer

buffers-modes

Direct Buffer

  • Direct Buffer:在java堆外分配,可以在java程序中访问的内存。使用-XX:MaxDirectMemorySize=512M设置大小。回收的时候是基于cleaner和幻象引用机制,其内部实现了一个Deallocator负责销毁的逻辑。对其销毁往往要拖到full GC的时候。
  • MappedByteBuffer:将文件按照指定大小直接映射为内存区域,访问时直接操作这块数据,省去了将数据从内和空间向用户空间传输的损耗。

使用

BytBuffer分配在堆内:ByteBuffer buf = ByteBuffer.allocate(1024);
ByteBuffer分配在堆外ByteBuffer buf = ByteBuffer.allocateDirect(1024);

对比一下allocate方法和allocateDirect方法:

    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }

    public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }

DirectBuffer.allocateDirect(int capacity)是调用了本地方法malloc去申请一块内存。

优缺点

创建效率

DirectBuffer创建效率其实比Heap Buffer低,因为java对象在java堆内申请内存只需要不足10条机器指令,而Direct Buffer调用malloc需要60~100条CPU指令。

读写效率

DirectBuffer比较快:

    static int write(FileDescriptor fd, ByteBuffer src, long position,
                     NativeDispatcher nd) 
        throws IOException
    {   
        if (src instanceof DirectBuffer)
            return writeFromNativeBuffer(fd, src, position, nd);

        // Substitute a native buffer
        int pos = src.position();
        int lim = src.limit();
        assert (pos <= lim);
        int rem = (pos <= lim ? lim - pos : 0); 
        ByteBuffer bb = Util.getTemporaryDirectBuffer(rem);
        try {
            bb.put(src);
            bb.flip();
        // ................略

可以看到,如果是direct,则调用writeFromNativeBuffer,如果不是,则需要创建一个临时的Buffer,把src拷贝进去。之所以这样做,是因为writeFromNativeBuffer的实现,会把buffer的地址传给OS,这块内存不能动,但是GC可能会搬动java推的东西,所以只能弄一块地址不会变的内存。

问题:

  1. 操作系统可能会把 DirectByteBuffer 的内存交换到磁盘上,这样势必会影响性能,为了避免这个问题我们不得不对操作系统做相应的配置;
  2. DirectByteBuffer 申请内存失败会直接抛出 OutOfMemoryError。

对于Direct Buffer回收的建议

  • 应用程序中显示调用System.gc();
  • 框架自己在程序中调用释放方法;
  • 重复使用Direct Buffer。
comments powered by Disqus