文件復(fù)制的4種實現(xiàn)方式及性能對比

盡管Java提供了java.io.File這樣一個操作文件的類,但并沒有提供一個復(fù)制文件的方法。

然而,當我們需要對磁盤上的文件進行處理的時候,這是一個很重要的方法。在這個時候我們往往不得不自己實現(xiàn)這樣一個完成文件復(fù)制操作的方法。下面將會介紹4種常見的文件復(fù)制的實現(xiàn),并比較下它們的性能。

使用FileStream

能找到的最常見經(jīng)典例子。從文件A的輸入流讀取一批字節(jié),寫到文件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);
    }
}   

如你所見,這種實現(xiàn)方式需要多次讀取數(shù)據(jù),再寫入將數(shù)據(jù)寫入,因此受限于我們提供的buffer的大小,他的效率有點一般。

使用FileChannel

Java NIO類庫里引入了一個叫transferFrom的方法,文檔里說這是一個會比FileStream方式更快的復(fù)制操作。

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)方法用于文件復(fù)制,如果項目里使用到了這個類庫,使用這個方法是個不錯的選擇。它的內(nèi)部也是使用Java NIO的FileChannel實現(xiàn)的。

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

使用Java 7 的Files類

如果對Java 7 的使用有經(jīng)驗的話,那應(yīng)該接觸過Files這個工具類。

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

性能

由于項目里沒用到Apache Common IO,Android僅支持Java 7的語法特性,因此我只測試了前兩種,數(shù)據(jù)如下:

(這里更正下,Android支持Java 7的語法特性,但核心類庫的支持并不完整,比如上文提及的Files類、awt包等就屬于不支持的范圍。)

復(fù)制文件,大小2M

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

復(fù)制文件,大小4.5M

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

復(fù)制文件,大小8M

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

復(fù)制文件,大小161M

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

結(jié)論

從數(shù)據(jù)上可以看出,使用FileStream的方式,復(fù)制的效率跟我們的buffer大小取值關(guān)系很大,這無疑加大了我們使用它進行文件復(fù)制的負擔。

而NIO的方式則不然,無論是小文件、還是大文件,它的效率都跟我們測試FileStream的最好水平相當!

因此,把FileStream這種老舊的實現(xiàn)方式從項目里挪走吧,是時候用上FileChannel了。


2016年06月16日 14:04 更新

附錄:FileChannel的內(nèi)部實現(xiàn)

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大小,繼續(xù)寫入,直到完全寫完。

需要注意到的是,buffer并不是我們平時使用的byte數(shù)組,而是MappedByteBuffer對象,這是java nio引入的文件內(nèi)存映射方案,讀寫性能很高。

Written with StackEdit.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容