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

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

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

使用FileStream

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

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

使用FileChannel

Java NIO類(lèi)庫(kù)里引入了一個(gè)叫transferFrom的方法,文檔里說(shuō)這是一個(gè)會(huì)比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 提供了一個(gè)FileUtils.copyFile(File from, File to)方法用于文件復(fù)制,如果項(xiàng)目里使用到了這個(gè)類(lèi)庫(kù),使用這個(gè)方法是個(gè)不錯(cuò)的選擇。它的內(nèi)部也是使用Java NIO的FileChannel實(shí)現(xiàn)的。

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

使用Java 7 的Files類(lèi)

如果對(duì)Java 7 的使用有經(jīng)驗(yàn)的話,那應(yīng)該接觸過(guò)Files這個(gè)工具類(lèi)。

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

性能

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

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

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

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

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

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

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

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

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

buffer大小 耗時(shí)
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)系很大,這無(wú)疑加大了我們使用它進(jìn)行文件復(fù)制的負(fù)擔(dān)。

而NIO的方式則不然,無(wú)論是小文件、還是大文件,它的效率都跟我們測(cè)試FileStream的最好水平相當(dāng)!

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


2016年06月16日 14:04 更新

附錄:FileChannel的內(nèi)部實(shí)現(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,寫(xiě)到輸出去,Buffer的大小最大為8388608,如果剩余的文件長(zhǎng)度小于這個(gè)值,則用剩余文件長(zhǎng)度的大小為buffer大小,繼續(xù)寫(xiě)入,直到完全寫(xiě)完。

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

Written with StackEdit.

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

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

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