java 文件复制的几种方式比较

java文件复制的几种方式比较

1.文件复制的几种实现方式

1.1使用java文件工具类Files

/**
 * 文件复制 使用java文件工具类Files
 *
 * @param srcFile  源文件
 * @param destFile 目标文件
 * @throws IOException IO异常
 */
public static void copyByJavaFiles(File srcFile, File destFile) throws IOException {
    Files.copy(srcFile.toPath(), new BufferedOutputStream(new FileOutputStream(destFile)));
}

1.2使用FileInputStream , FileOutputStream

/**
 * 文件复制 使用FileInputStream , FileOutputStream
 *
 * @param srcFile  源文件
 * @param destFile 目标文件
 * @throws IOException IO异常
 */
public static void copyByInOutStream(File srcFile, File destFile) throws IOException {
    byte[] bytes = new byte[24 * 1024];
    try (InputStream in = new FileInputStream(srcFile);
         OutputStream out = new FileOutputStream(destFile)) {
        int count;
        while ((count = in.read(bytes)) > 0) {
            out.write(bytes, 0, count);
        }
    }
}

1.3使用BufferedInputStream , BufferedOutputStream

/**
 * 文件复制 使用BufferedInputStream , BufferedOutputStream
 *
 * @param srcFile  源文件
 * @param destFile 目标文件
 * @throws IOException IO异常
 */
public static void copyByBufferedInOutStream(File srcFile, File destFile) throws IOException {
    byte[] bytes = new byte[24 * 1024];
    try (InputStream in = new BufferedInputStream(new FileInputStream(srcFile));
         OutputStream out = new BufferedOutputStream(new FileOutputStream(destFile))) {
        int count;
        while ((count = in.read(bytes)) > 0) {
            out.write(bytes, 0, count);
        }
    }
}

1.4使用nio FileChannel + ByteBuffer

/**
 * 文件复制 使用nio FileChannel + ByteBuffer
 *
 * @param srcFile  源文件
 * @param destFile 目标文件
 * @throws IOException IO异常
 */
public static void copyByNioByteBuffer(File srcFile, File destFile) throws IOException {
    ByteBuffer buffer = ByteBuffer.allocate(24 * 1024);
    try (FileChannel inChannel = new FileInputStream(srcFile).getChannel();
         FileChannel outChannel = new FileOutputStream(destFile).getChannel()) {
        while (inChannel.read(buffer) != -1) {
            buffer.flip();
            while (buffer.hasRemaining()) {
                outChannel.write(buffer);
            }
            buffer.clear();
        }
    }
}

知识补充:

缓冲区(Buffer)就是在内存中预留指定大小的存储空间用来对输入/输出(I/O)的数据作临时存储,ByteBuffer是缓冲区的一种

缓冲区都有4个属性:capacity、limit、position、mark

属性 描述
capacity 容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变
limit 表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的
position 位置,下一个要被读或写的元素位置索引,每次读写缓冲区数据时都会改变改值,为下次读写作准备
mark 标记,调用mark()来设置mark=position,再调用reset()可以让position恢复到标记的位置

ByteBuffer的方法

buffer.flip() 翻转 , 执行后将属性 limit = position;position = 0;mark = -1;position和limit之间即为可以读取到的有效数据,总的作用是将一个处于存数据状态的缓冲区变为一个处于准备取数据的状态的缓冲区;

buffer.clear() 属性初始, 执行后将属性position = 0;limit = capacity;mark = -1; 初始化, 但是并不影响底层byte数组的内容;

1.5使用nio FileChannel + transferTo (超过2g的文件需要分批)

/**
 * 文件复制 使用nio FileChannel + transferTo (超过2g的文件需要分批)
 *
 * @param srcFile  源文件
 * @param destFile 目标文件
 * @throws IOException IO异常
 */
public static void copyByNioTransferTo(File srcFile, File destFile) throws IOException {
    try (FileChannel inChannel = new FileInputStream(srcFile).getChannel();
         FileChannel outChannel = new FileOutputStream(destFile).getChannel()) {
        long size = inChannel.size();
        long pos = 0;
        long count;
        while (pos < size) {
            count = Math.min(size - pos, 2 * 1024 * 1024 * 1024L - 1);
//                pos += outChannel.transferFrom(inChannel, pos, count);
            pos += inChannel.transferTo(pos, count, outChannel);
        }
    }
}

1.6.使用common-io 中的FileUtils

需要在pom中引入commons-io

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>
/**
 * 文件复制 使用common-io 中的FileUtils
 *
 * @param srcFile  源文件
 * @param destFile 目标文件
 * @throws IOException IO异常
 */
public static void copyByCommonIoFileUtils(File srcFile, File destFile) throws IOException {
    FileUtils.copyFile(srcFile, destFile);
}

2.完整代码以及测试

2.1工具类代码

/**
 * File的工具类
 */
public final class FileUtil {
    /**
     * 缓存byte数组的默认的大小
     */
    private static final int DEFAULT_BUFFER_BYTES_LENGTH = 24 * 1024;

    /**
     * transferTo 或者 transferFrom 最大的限制 2g - 1
     */
    private static final long CHANNEL_TRANSFER_BUFFER_SIZE = 2 * 1024 * 1024 * 1024L - 1;

    /**
     * 单位
     */
    private static final String[] UNITS = new String[]{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB"};

    /**
     * 点号
     */
    private static final String POINT = ".";

    private FileUtil() {
    }

    /**
     * 字节 转换 最大单位
     *
     * @param bytesCount 文件大小 默认是字节
     * @return String
     */
    public static String convertUnit(long bytesCount) {
        int index = 0;
        long k = bytesCount;
        if (bytesCount >= 1024) {
            do {
                k = k / 1024;
                index++;
            } while (!(k < 1024));
        }
        return k + UNITS[index];
    }

    /**
     * 检测文件参数是否满足复制的条件
     *
     * @param srcFile  源文件
     * @param destFile 目标文件
     * @throws FileNotFoundException fileNotFoundException
     */
    private static void requireCanCopyFile(File srcFile, File destFile) throws FileNotFoundException {
        Objects.requireNonNull(srcFile, "the params 'srcFile' is must not null");
        if (!srcFile.exists()) {
            throw new FileNotFoundException("the 'srcFile' is not found");
        }
        Objects.requireNonNull(destFile, "the params 'destFile' is must not null");
    }

    /**
     * 文件复制 使用java文件工具类Files
     *
     * @param srcFile  源文件
     * @param destFile 目标文件
     * @throws IOException IO异常
     */
    public static void copyByJavaFiles(File srcFile, File destFile) throws IOException {
        requireCanCopyFile(srcFile, destFile);
        Files.copy(srcFile.toPath(), new BufferedOutputStream(new FileOutputStream(destFile)));
    }

    /**
     * 文件复制 使用FileInputStream , FileOutputStream
     *
     * @param srcFile  源文件
     * @param destFile 目标文件
     * @throws IOException IO异常
     */
    public static void copyByInOutStream(File srcFile, File destFile) throws IOException {
        requireCanCopyFile(srcFile, destFile);
        byte[] bytes = new byte[DEFAULT_BUFFER_BYTES_LENGTH];
        try (InputStream in = new FileInputStream(srcFile);
             OutputStream out = new FileOutputStream(destFile)) {
            int count;
            while ((count = in.read(bytes)) > 0) {
                out.write(bytes, 0, count);
            }
        }
    }

    /**
     * 文件复制 使用BufferedInputStream , BufferedOutputStream
     *
     * @param srcFile  源文件
     * @param destFile 目标文件
     * @throws IOException IO异常
     */
    public static void copyByBufferedInOutStream(File srcFile, File destFile) throws IOException {
        requireCanCopyFile(srcFile, destFile);
        byte[] bytes = new byte[DEFAULT_BUFFER_BYTES_LENGTH];
        try (InputStream in = new BufferedInputStream(new FileInputStream(srcFile));
             OutputStream out = new BufferedOutputStream(new FileOutputStream(destFile))) {
            int count;
            while ((count = in.read(bytes)) > 0) {
                out.write(bytes, 0, count);
            }
        }
    }

    /**
     * 文件复制 使用nio FileChannel + ByteBuffer
     *
     * @param srcFile  源文件
     * @param destFile 目标文件
     * @throws IOException IO异常
     */
    public static void copyByNioByteBuffer(File srcFile, File destFile) throws IOException {
        requireCanCopyFile(srcFile, destFile);
        ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_BUFFER_BYTES_LENGTH);
        try (FileChannel inChannel = new FileInputStream(srcFile).getChannel();
             FileChannel outChannel = new FileOutputStream(destFile).getChannel()) {
            while (inChannel.read(buffer) != -1) {
                buffer.flip();
                while (buffer.hasRemaining()) {
                    outChannel.write(buffer);
                }
                buffer.clear();
            }
        }
    }

    /**
     * 文件复制 使用nio FileChannel + transferTo (超过2g的文件需要分批)
     *
     * @param srcFile  源文件
     * @param destFile 目标文件
     * @throws IOException IO异常
     */
    public static void copyByNioTransferTo(File srcFile, File destFile) throws IOException {
        requireCanCopyFile(srcFile, destFile);
        try (FileChannel inChannel = new FileInputStream(srcFile).getChannel();
             FileChannel outChannel = new FileOutputStream(destFile).getChannel()) {
            long size = inChannel.size();
            long pos = 0;
            long count;
            while (pos < size) {
                count = Math.min(size - pos, CHANNEL_TRANSFER_BUFFER_SIZE);
//                pos += outChannel.transferFrom(inChannel, pos, count);
                pos += inChannel.transferTo(pos, count, outChannel);
            }
        }
    }

    /**
     * 文件复制 使用common-io 中的FileUtils
     *
     * @param srcFile  源文件
     * @param destFile 目标文件
     * @throws IOException IO异常
     */
    public static void copyByCommonIoFileUtils(File srcFile, File destFile) throws IOException {
        FileUtils.copyFile(srcFile, destFile);
    }
}

2.2测试类代码

/**
 * 文件工具类测试
 */
public class FileUtilTest {
    private static final Logger slf4jLog = LoggerFactory.getLogger(FileUtilTest.class);

    public static void testCopy(File sourceFile) {
        try {
            // 测试使用FileInputStream ,FileOutputStream
            long startTime1 = System.currentTimeMillis();
            File destFile = new File(sourceFile.getCanonicalPath().replaceAll("(\\.[A-Za-z]+)$",
                    "copyByInOutStream-$1"));
            FileUtil.copyByInOutStream(sourceFile, destFile);
            slf4jLog.debug("copyByInOutStream() take time : " + (System.currentTimeMillis() - startTime1) + " ms");
        } catch (IOException e) {
            slf4jLog.error(e.getMessage());
            e.printStackTrace();
        }

        try {
            // 测试使用BufferedInputStream , BufferedOutputStream
            long startTime2 = System.currentTimeMillis();
            File destFile = new File(sourceFile.getCanonicalPath().replaceAll("(\\.[A-Za-z]+)$",
                    "copyByBufferedInOutStream-$1"));
            FileUtil.copyByBufferedInOutStream(sourceFile, destFile);
            slf4jLog.debug("copyByBufferedInOutStream() take time : " + (System.currentTimeMillis() - startTime2) + " ms");
        } catch (IOException e) {
            slf4jLog.error(e.getMessage());
            e.printStackTrace();
        }

        try {
            // 测试使用java工具类Files
            long startTime3 = System.currentTimeMillis();
            File destFile = new File(sourceFile.getCanonicalPath().replaceAll("(\\.[A-Za-z]+)$",
                    "copyByJavaFiles-$1"));
            FileUtil.copyByJavaFiles(sourceFile, destFile);
            slf4jLog.debug("copyByJavaFiles() take time : " + (System.currentTimeMillis() - startTime3) + " ms");
        } catch (IOException e) {
            slf4jLog.error(e.getMessage());
            e.printStackTrace();
        }

        try {
            // 测试使用nio FileChannel + ByteBuffer
            long startTime4 = System.currentTimeMillis();
            File destFile = new File(sourceFile.getCanonicalPath().replaceAll("(\\.[A-Za-z]+)$",
                    "copyByNioByteBuffer-$1"));
            FileUtil.copyByNioByteBuffer(sourceFile, destFile);
            slf4jLog.debug("copyByNioByteBuffer() take time : " + (System.currentTimeMillis() - startTime4) + " ms");
        } catch (IOException e) {
            slf4jLog.error(e.getMessage());
            e.printStackTrace();
        }

        try {
            // 测试使用nio FileChannel + TransferTo()/transferFrom() ps:注意大于2g的文件不能用这种方式
            long startTime5 = System.currentTimeMillis();
            File destFile = new File(sourceFile.getCanonicalPath().replaceAll("(\\.[A-Za-z]+)$",
                    "copyByNioTransferTo-$1"));
            FileUtil.copyByNioTransferTo(sourceFile, destFile);
            slf4jLog.debug("copyByNioTransferTo() take time : " + (System.currentTimeMillis() - startTime5) + " ms");
        } catch (IOException e) {
            slf4jLog.error(e.getMessage());
            e.printStackTrace();
        }

        try {
            // 测试使用common-io FileUtils.copyFile()工具包
            long startTime6 = System.currentTimeMillis();
            File destFile = new File(sourceFile.getCanonicalPath().replaceAll("(\\.[A-Za-z]+)$",
                    "copyByCommonIoFileUtils-$1"));
            FileUtil.copyByCommonIoFileUtils(sourceFile, destFile);
            slf4jLog.debug("copyByCommonIoFileUtils() take time : " + (System.currentTimeMillis() - startTime6) + " ms");
        } catch (IOException e) {
            slf4jLog.error(e.getMessage());
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        // 创建一些测试文件
//        Runtime.getRuntime().exec("fsutil file createnew D:\\download\\测试文件读写\\0-200MB.doc " + 200 * 1024* 1024L);
//        Runtime.getRuntime().exec("fsutil file createnew D:\\download\\测试文件读写\\1-400MB.doc " + 400 * 1024* 1024L);
//        Runtime.getRuntime().exec("fsutil file createnew D:\\download\\测试文件读写\\3-600MB.doc " + 600 * 1024* 1024L);
//        Runtime.getRuntime().exec("fsutil file createnew D:\\download\\测试文件读写\\4-800MB.doc " + 800 * 1024* 1024L);
//
//        Runtime.getRuntime().exec("fsutil file createnew D:\\download\\测试文件读写\\5-2GB.doc " + 2 * 1024 * 1024* 1024L);
//        Runtime.getRuntime().exec("fsutil file createnew D:\\download\\测试文件读写\\6-3GB.doc " + 3 * 1024 * 1024 * 1024L);
//        Runtime.getRuntime().exec("fsutil file createnew D:\\download\\测试文件读写\\7-10GB.doc " + 10 * 1024 * 1024 * 1024L);
//
        slf4jLog.debug("testCopy start...");
        File dir = new File("D:\\download\\测试文件读写\\");
        for (File file : Objects.requireNonNull(dir.listFiles())) {
            slf4jLog.debug("=======================================分割线==============================================");
            slf4jLog.debug("file size : " + FileUtil.convertUnit(file.length()));
            testCopy(file);
        }
    }
}

2.3测试结果

测试byte[] bytes = new byte[1 * 1024]时的测试结果

testCopy start...
=======================================分割线==============================================
file size : 200MB
copyByInOutStream() take time : 4182 ms
copyByBufferedInOutStream() take time : 693 ms
copyByJavaFiles() take time : 720 ms
copyByNioByteBuffer() take time : 4013 ms
copyByNioTransferTo() take time : 501 ms
copyByCommonIoFileUtils() take time : 1979 ms
=======================================分割线==============================================
file size : 400MB
copyByInOutStream() take time : 8243 ms
copyByBufferedInOutStream() take time : 1374 ms
copyByJavaFiles() take time : 1364 ms
copyByNioByteBuffer() take time : 8167 ms
copyByNioTransferTo() take time : 537 ms
copyByCommonIoFileUtils() take time : 398 ms
=======================================分割线==============================================
file size : 600MB
copyByInOutStream() take time : 12087 ms
copyByBufferedInOutStream() take time : 2084 ms
copyByJavaFiles() take time : 1992 ms
copyByNioByteBuffer() take time : 11969 ms
copyByNioTransferTo() take time : 851 ms
copyByCommonIoFileUtils() take time : 549 ms
=======================================分割线==============================================
file size : 800MB
copyByInOutStream() take time : 16175 ms
copyByBufferedInOutStream() take time : 2822 ms
copyByJavaFiles() take time : 2671 ms
copyByNioByteBuffer() take time : 15952 ms
copyByNioTransferTo() take time : 1110 ms
copyByCommonIoFileUtils() take time : 594 ms
=======================================分割线==============================================
file size : 2GB
copyByInOutStream() take time : 41318 ms
copyByBufferedInOutStream() take time : 6771 ms
copyByJavaFiles() take time : 6597 ms
copyByNioByteBuffer() take time : 40908 ms
copyByNioTransferTo() take time : 2165 ms
copyByCommonIoFileUtils() take time : 2619 ms
=======================================分割线==============================================
file size : 3GB
copyByInOutStream() take time : 62443 ms
copyByBufferedInOutStream() take time : 10344 ms
copyByJavaFiles() take time : 10297 ms
copyByNioByteBuffer() take time : 63058 ms
copyByNioTransferTo() take time : 3052 ms
copyByCommonIoFileUtils() take time : 5407 ms

Process finished with exit code -1

测试byte[] bytes = new byte[24 * 1024]时的测试结果

testCopy start...
=======================================分割线==============================================
file size : 200MB
copyByInOutStream() take time : 932 ms
copyByBufferedInOutStream() take time : 772 ms
copyByJavaFiles() take time : 731 ms
copyByNioByteBuffer() take time : 714 ms
copyByNioTransferTo() take time : 174 ms
copyByCommonIoFileUtils() take time : 187 ms
=======================================分割线==============================================
file size : 400MB
copyByInOutStream() take time : 1583 ms
copyByBufferedInOutStream() take time : 1537 ms
copyByJavaFiles() take time : 1433 ms
copyByNioByteBuffer() take time : 1356 ms
copyByNioTransferTo() take time : 346 ms
copyByCommonIoFileUtils() take time : 376 ms
=======================================分割线==============================================
file size : 600MB
copyByInOutStream() take time : 2020 ms
copyByBufferedInOutStream() take time : 2116 ms
copyByJavaFiles() take time : 2054 ms
copyByNioByteBuffer() take time : 2331 ms
copyByNioTransferTo() take time : 919 ms
copyByCommonIoFileUtils() take time : 1137 ms
=======================================分割线==============================================
file size : 800MB
copyByInOutStream() take time : 3465 ms
copyByBufferedInOutStream() take time : 3328 ms
copyByJavaFiles() take time : 3159 ms
copyByNioByteBuffer() take time : 3106 ms
copyByNioTransferTo() take time : 1268 ms
copyByCommonIoFileUtils() take time : 1348 ms
=======================================分割线==============================================
file size : 2GB
copyByInOutStream() take time : 8118 ms
copyByBufferedInOutStream() take time : 7953 ms
copyByJavaFiles() take time : 7924 ms
copyByNioByteBuffer() take time : 7900 ms
copyByNioTransferTo() take time : 3481 ms
copyByCommonIoFileUtils() take time : 3291 ms

Process finished with exit code 0

大文件10G测试结果

=======================================分割线==============================================
file size : 10GB
copyByInOutStream() take time : 16928 ms
copyByBufferedInOutStream() take time : 17053 ms
copyByJavaFiles() take time : 34347 ms
copyByNioByteBuffer() take time : 17218 ms
copyByNioTransferTo() take time : 17226 ms
copyByCommonIoFileUtils() take time : 18619 ms

3. 总结

1.bytes缓存的大小对copyByInOutStream()的速度影响很大,选择合适的大小copyByInOutStream()的速度可以和copyByBufferedInOutStream()基本相当;

2.在文件在一定的大小内(2G左右[不准确],具体的数值需要大量的测试)copyByNioTransferTo()copyByCommonIoFileUtils()无疑是最快的;

3.文件很大的情况下(例如10G)各种复制方式速度基本趋于一致;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容