美文网首页
文件复制的4种实现方式及性能对比

文件复制的4种实现方式及性能对比

作者: 罗力 | 来源:发表于2016-06-15 21:43 被阅读1430次

尽管Java提供了java.io.File这样一个操作文件的类,但并没有提供一个复制文件的方法。

然而,当我们需要对磁盘上的文件进行处理的时候,这是一个很重要的方法。在这个时候我们往往不得不自己实现这样一个完成文件复制操作的方法。下面将会介绍4种常见的文件复制的实现,并比较下它们的性能。

使用FileStream

能找到的最常见经典例子。从文件A的输入流读取一批字节,写到文件B的输出流。

public static void copy(String from, String to, int bufferSize) {
    InputStream in = null;
    OutputStream out = null;
    try {
        in = new FileInputStream(new File(from));
        out = new FileOutputStream(new File(to));

        byte[] buffer = new byte[bufferSize];
        int len;

        while ((len = in.read(buffer)) > 0) {
            out.write(buffer, 0, len);
        }
    } catch (Exception e) {
        Log.w(TAG + ":copy", "error occur while copy", e);
    } finally {
        safelyClose(TAG + ":copy", in);
        safelyClose(TAG + ":copy", out);
    }
}   

如你所见,这种实现方式需要多次读取数据,再写入将数据写入,因此受限于我们提供的buffer的大小,他的效率有点一般。

使用FileChannel

Java NIO类库里引入了一个叫transferFrom的方法,文档里说这是一个会比FileStream方式更快的复制操作。

public static void copyNio(String from, String to) {
    FileChannel input = null;
    FileChannel output = null;

    try {
        input = new FileInputStream(new File(from)).getChannel();
        output = new FileOutputStream(new File(to)).getChannel();
        output.transferFrom(input, 0, input.size());
    } catch (Exception e) {
        Log.w(TAG + "copyNio", "error occur while copy", e);
    } finally {
        safelyClose(TAG + "copyNio", input);
        safelyClose(TAG + "copyNio", output);
    }
}

使用Apache Commons IO

Appache Commons IO 提供了一个FileUtils.copyFile(File from, File to)方法用于文件复制,如果项目里使用到了这个类库,使用这个方法是个不错的选择。它的内部也是使用Java NIO的FileChannel实现的。

private static void  copyFileUsingApacheCommonsIO(File source, File dest) throws IOException {
    FileUtils.copyFile(source, dest);
}

使用Java 7 的Files类

如果对Java 7 的使用有经验的话,那应该接触过Files这个工具类。

private static void copyFileUsingJava7Files(File source, File dest) throws IOException {
    Files.copy(source.toPath(), dest.toPath());
}

性能

由于项目里没用到Apache Common IO,Android仅支持Java 7的语法特性,因此我只测试了前两种,数据如下:

(这里更正下,Android支持Java 7的语法特性,但核心类库的支持并不完整,比如上文提及的Files类、awt包等就属于不支持的范围。)

复制文件,大小2M

buffer大小 耗时
512 455
1024 238
2048 131
4096 82
8192 46
16384 36
32768 30
65536 29
131072 26
NIO方式 31

复制文件,大小4.5M

buffer大小 耗时
512 937
1024 523
2048 315
4096 155
8192 104
16384 108
32768 83
65536 74
131072 79
NIO方式 75

复制文件,大小8M

buffer大小 耗时
512 1774
1024 942
2048 488
4096 311
8192 225
16384 169
32768 154
65536 129
131072 121
NIO方式 108

复制文件,大小161M

buffer大小 耗时
512 38561
1024 19994
2048 10747
4096 5500
8192 3857
16384 3327
32768 3201
65536 3288
131072 3281
NIO方式 3266

结论

从数据上可以看出,使用FileStream的方式,复制的效率跟我们的buffer大小取值关系很大,这无疑加大了我们使用它进行文件复制的负担。

而NIO的方式则不然,无论是小文件、还是大文件,它的效率都跟我们测试FileStream的最好水平相当!

因此,把FileStream这种老旧的实现方式从项目里挪走吧,是时候用上FileChannel了。


2016年06月16日 14:04 更新

附录:FileChannel的内部实现

private long transferFromFileChannel(FileChannelImpl input, long start, long length) throws IOException {
    if(!input.readable) {
        throw new NonReadableChannelException();
    } else {
        Object lock = input.positionLock;
        synchronized(lock) {
            long position = input.position();
            long total = Math.min(length, input.size() - position);
            long remain = total;
            long current = position;

            long bufferSize;
            while(remain > 0L) {
                bufferSize = Math.min(remain, 8388608L);
                MappedByteBuffer buffer = input.map(MapMode.READ_ONLY, current, bufferSize);

                try {
                    long written = (long)this.write(buffer, start);

                    assert written > 0L;

                    current += written;
                    start += written;
                    remain -= written;
                } catch (IOException e) {
                    if(remain != total) {
                        break;
                    }

                    throw e;
                } finally {
                    unmap(buffer);
                }
            }

            bufferSize = total - remain;
            input.position(position + bufferSize);
            return bufferSize;
        }
    }
}

大致就是从输入里映射一部分作为buffer,写到输出去,Buffer的大小最大为8388608,如果剩余的文件长度小于这个值,则用剩余文件长度的大小为buffer大小,继续写入,直到完全写完。

需要注意到的是,buffer并不是我们平时使用的byte数组,而是MappedByteBuffer对象,这是java nio引入的文件内存映射方案,读写性能很高。

Written with StackEdit.

相关文章

  • Java 文件复制的实现方式及性能对比

    尽管Java提供了java.io.File这样一个操作文件的类,但并没有提供一个复制文件的方法。

  • 文件复制的4种实现方式及性能对比

    尽管Java提供了java.io.File这样一个操作文件的类,但并没有提供一个复制文件的方法。 然而,当我们需要...

  • Redis——主从复制

    主从复制 : 主从复制实现:slaveof配置文件:slave ip port 复制方式全量复制全量复制全量复制开...

  • java_NIO

    FileChannel通道核心要点 使用FileChannel配合缓冲区实现文件复制 使用内存映射文件的方式实现文...

  • Redis 高级(主从,哨兵,集群)

    一、主从复制 Redis 的主从复制特点 1. 配置主从 实现方式同样有两种: 命令方式和配置文件方式 命令方式 ...

  • Redis 高级部分

    一、主从复制 Rdis 的主从复制特点 1. 配置主从 实现方式同样有两种: 命令方式和配置文件方式 命令方式 只...

  • Redis 高级部分

    一、主从复制 Rdis 的主从复制特点 1. 配置主从 实现方式同样有两种: 命令方式和配置文件方式 命令方式 只...

  • Python---实现简单的项目打包、备份

    About Project 用python实现通过文件及目录创建、复制等操作,实现简单的项目打包、备份等源码地址 ...

  • Mysql 5.7 主从复制的多线程复制配置方式

    Mysql 5.7 主从复制的多线程复制配置方式 数据库复制的主要性能问题就是数据延时 为了优化复制性能,Mysq...

  • 数据结构基础学习之(内排序)

    学习知识 排序基本概念 插入排序的实现方法及性能分析 交换排序的实现方法及性能分析 选择排序的实现方法及性能分析 ...

网友评论

      本文标题:文件复制的4种实现方式及性能对比

      本文链接:https://www.haomeiwen.com/subject/wuwpdttx.html