1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        文件寫(xiě)入的6種方法,這種方法性能最好

        共 5015字,需瀏覽 11分鐘

         ·

        2020-12-25 07:49

        作者 | 王磊

        來(lái)源 | Java中文社群(ID:javacn666)

        轉(zhuǎn)載請(qǐng)聯(lián)系授權(quán)(微信ID:GG_Stone)

        在 Java 中操作文件的方法本質(zhì)上只有兩種:字符流和字節(jié)流,而字節(jié)流和字符流的實(shí)現(xiàn)類(lèi)又有很多,因此在文件寫(xiě)入時(shí)我們就可以選擇各種各樣的類(lèi)來(lái)實(shí)現(xiàn)。我們本文就來(lái)盤(pán)點(diǎn)一下這些方法,順便測(cè)試一下它們性能,以便為我們選出最優(yōu)的寫(xiě)入方法。

        在正式開(kāi)始之前,我們先來(lái)了解幾個(gè)基本的概念:流、字節(jié)流和字符流的定義與區(qū)別。

        0.什么是流?

        Java 中的“流”是一種抽象的概念,也是一種比喻,就好比水流一樣,水流是從一端流向另一端的,而在 Java 中的“水流”就是數(shù)據(jù),數(shù)據(jù)會(huì)從一端“流向”另一端。

        根據(jù)流的方向性,我們可以將流分為輸入流和輸出流,當(dāng)程序需要從數(shù)據(jù)源中讀入數(shù)據(jù)的時(shí)候就會(huì)開(kāi)啟一個(gè)輸入流,相反,寫(xiě)出數(shù)據(jù)到某個(gè)數(shù)據(jù)源目的地的時(shí)候也會(huì)開(kāi)啟一個(gè)輸出流,數(shù)據(jù)源可以是文件、內(nèi)存或者網(wǎng)絡(luò)等。

        1.什么是字節(jié)流?

        字節(jié)流的基本單位為字節(jié)(Byte),一個(gè)字節(jié)通常為 8 位,它是用來(lái)處理二進(jìn)制(數(shù)據(jù))的。字節(jié)流有兩個(gè)基類(lèi):InputStream(輸入字節(jié)流)和 OutputStream(輸出字節(jié)流)。

        常用字節(jié)流的繼承關(guān)系圖如下圖所示:其中 InputStream 用于讀操作,而 OutputStream 用于寫(xiě)操作。

        2.什么是字符流?

        字符流的基本單位為 Unicode,大小為兩個(gè)字節(jié)(Byte),它通常用來(lái)處理文本數(shù)據(jù)。字符流的兩個(gè)基類(lèi):Reader(輸入字符流)和 Writer(輸出字符流)。

        常用字符流的繼承關(guān)系圖如下圖所示:

        3.流的分類(lèi)

        流可以根據(jù)不同的維度進(jìn)行分類(lèi),比如可以根據(jù)流的方向進(jìn)行分類(lèi),也可以根據(jù)傳輸?shù)膯挝贿M(jìn)行分類(lèi),還可以根據(jù)流的功能進(jìn)行分類(lèi),比如以下幾個(gè)。

        ① 按流向分類(lèi)

        • 輸出流:OutputStreamWriter?為基類(lèi)。
        • 輸入流:InputStreamReader 為基類(lèi)。

        ② 根據(jù)傳輸數(shù)據(jù)單位分類(lèi)

        • 字節(jié)流:OutputStreamInputStream 為基類(lèi)。
        • 字符流:Writer?和 Reader?為基類(lèi)。

        ③ 根據(jù)功能分類(lèi)

        • 字節(jié)流:可以從或向一個(gè)特定的地方(節(jié)點(diǎn))讀寫(xiě)數(shù)據(jù)。
        • 處理流:是對(duì)一個(gè)已存在的流的連接和封裝,通過(guò)所封裝的流的功能調(diào)用實(shí)現(xiàn)數(shù)據(jù)讀寫(xiě)。

        PS:我們通常是以傳輸數(shù)據(jù)的單位來(lái)為流進(jìn)行分類(lèi)。

        4.寫(xiě)文件的6種方法

        寫(xiě)入文件的方法主要源于字符流 Writer?和輸出字節(jié)流 OutputStream?的子類(lèi),如下圖所示:

        以上標(biāo)注?號(hào)的類(lèi)就是用來(lái)實(shí)現(xiàn)文件寫(xiě)入的類(lèi),除此之外,在 JDK 1.7 中還提供了 Files?類(lèi)用來(lái)實(shí)現(xiàn)對(duì)文件的各種操作,接下來(lái)我們分別來(lái)看。

        方法 1:FileWriter

        FileWriter?屬于「字符流」體系中的一員,也是文件寫(xiě)入的基礎(chǔ)類(lèi),它包含 5 個(gè)構(gòu)造函數(shù),可以傳遞一個(gè)具體的文件位置,或者 File?對(duì)象,第二參數(shù)表示是否要追加文件,默認(rèn)值為 false 表示重寫(xiě)文件內(nèi)容,而非追加文件內(nèi)容(關(guān)于如何追加文件,我們后面會(huì)講)。

        FileWriter 類(lèi)的實(shí)現(xiàn)如下:

        /**
        ??*?方法 1:使用 FileWriter 寫(xiě)文件
        ??*?@param?filepath?文件目錄
        ??*?@param?content??待寫(xiě)入內(nèi)容
        ??*?@throws?IOException
        ??*/

        public?static?void?fileWriterMethod(String?filepath,?String?content)?throws?IOException?{
        ????try?(FileWriter?fileWriter?=?new?FileWriter(filepath))?{
        ????????fileWriter.append(content);
        ????}
        }

        只需要傳入具體的文件路徑和待寫(xiě)入的內(nèi)容即可,調(diào)用代碼如下:

        public?static?void?main(String[]?args)?{
        ????fileWriterMethod("/Users/mac/Downloads/io_test/write1.txt",?"哈嘍,Java中文社群.");
        }

        然后我們打開(kāi)寫(xiě)入的文件,實(shí)現(xiàn)結(jié)果如下:

        關(guān)于資源釋放的問(wèn)題:在 JDK 7 以上的版本,我們只需要使用 try-with-resource 的方式就可以實(shí)現(xiàn)資源的釋放,就比如使用 try (FileWriter fileWriter = new FileWriter(filepath)) {...} 就可以實(shí)現(xiàn) FileWriter 資源的自動(dòng)釋放。

        方法 2:BufferedWriter

        BufferedWriter?也屬于字符流體系的一員,與 FileWriter?不同的是 BufferedWriter 自帶緩沖區(qū),因此它寫(xiě)入文件的性能更高(下文會(huì)對(duì)二者進(jìn)行測(cè)試)。

        小知識(shí)點(diǎn):緩沖區(qū)

        緩沖區(qū)又稱(chēng)為緩存,它是內(nèi)存空間的一部分。也就是說(shuō),在內(nèi)存空間中預(yù)留了一定的存儲(chǔ)空間,這些存儲(chǔ)空間用來(lái)緩沖輸入或輸出的數(shù)據(jù),這部分預(yù)留的空間就叫做緩沖區(qū)。

        緩沖區(qū)的優(yōu)勢(shì)以文件流的寫(xiě)入為例,如果我們不使用緩沖區(qū),那么每次寫(xiě)操作 CPU 都會(huì)和低速存儲(chǔ)設(shè)備也就是磁盤(pán)進(jìn)行交互,那么整個(gè)寫(xiě)入文件的速度就會(huì)受制于低速的存儲(chǔ)設(shè)備(磁盤(pán))。但如果使用緩沖區(qū)的話,每次寫(xiě)操作會(huì)先將數(shù)據(jù)保存在高速緩沖區(qū)內(nèi)存上,當(dāng)緩沖區(qū)的數(shù)據(jù)到達(dá)某個(gè)閾值之后,再將文件一次性寫(xiě)入到磁盤(pán)上。因?yàn)閮?nèi)存的寫(xiě)入速度遠(yuǎn)遠(yuǎn)大于磁盤(pán)的寫(xiě)入速度,所以當(dāng)有了緩沖區(qū)之后,文件的寫(xiě)入速度就被大大提升了。

        了解了緩存區(qū)的優(yōu)點(diǎn)之后,咱們回到本文的主題,接下來(lái)我們用 BufferedWriter 來(lái)文件的寫(xiě)入,實(shí)現(xiàn)代碼如下:

        /**
        ?*?方法 2:使用 BufferedWriter 寫(xiě)文件
        ?*?@param?filepath?文件目錄
        ?*?@param?content??待寫(xiě)入內(nèi)容
        ?*?@throws?IOException
        ?*/

        public?static?void?bufferedWriterMethod(String?filepath,?String?content)?throws?IOException?{
        ????try?(BufferedWriter?bufferedWriter?=?new?BufferedWriter(new?FileWriter(filepath)))?{
        ????????bufferedWriter.write(content);
        ????}
        }

        調(diào)用代碼和方法 1 類(lèi)似,這里就不再贅述了。

        方法 3:PrintWriter

        PrintWriter?也屬于字符流體系中的一員,它雖然叫“字符打印流”,但使用它也可以實(shí)現(xiàn)文件的寫(xiě)入,實(shí)現(xiàn)代碼如下:

        /**
        ?*?方法 3:使用 PrintWriter 寫(xiě)文件
        ?*?@param?filepath?文件目錄
        ?*?@param?content??待寫(xiě)入內(nèi)容
        ?*?@throws?IOException
        ?*/

        public?static?void?printWriterMethod(String?filepath,?String?content)?throws?IOException?{
        ????try?(PrintWriter?printWriter?=?new?PrintWriter(new?FileWriter(filepath)))?{
        ????????printWriter.print(content);
        ????}
        }

        從上述代碼可以看出,無(wú)論是 PrintWriter 還是 BufferedWriter 都必須基于 FileWriter?類(lèi)來(lái)完成調(diào)用。

        方法 4:FileOutputStream

        上面 3 個(gè)示例是關(guān)于字符流寫(xiě)入文件的一些操作,而接下來(lái)我們將使用字節(jié)流來(lái)完成文件寫(xiě)入。我們將使用 String?自帶的 getBytes() 方法先將字符串轉(zhuǎn)換成二進(jìn)制文件,然后再進(jìn)行文件寫(xiě)入,它的實(shí)現(xiàn)代碼如下:

        /**
        ?*?方法 4:使用 FileOutputStream 寫(xiě)文件
        ?*?@param?filepath?文件目錄
        ?*?@param?content??待寫(xiě)入內(nèi)容
        ?*?@throws?IOException
        ?*/

        public?static?void?fileOutputStreamMethod(String?filepath,?String?content)?throws?IOException?{
        ????try?(FileOutputStream?fileOutputStream?=?new?FileOutputStream(filepath))?{
        ????????byte[]?bytes?=?content.getBytes();
        ????????fileOutputStream.write(bytes);
        ????}
        }

        方法 5:BufferedOutputStream

        BufferedOutputStream?屬于字節(jié)流體系中的一員,與 FileOutputStream?不同的是,它自帶了緩沖區(qū)的功能,因此性能更好,它的實(shí)現(xiàn)代碼如下:

        /**
        ?*?方法 5:使用 BufferedOutputStream 寫(xiě)文件
        ?*?@param?filepath?文件目錄
        ?*?@param?content??待寫(xiě)入內(nèi)容
        ?*?@throws?IOException
        ?*/

        public?static?void?bufferedOutputStreamMethod(String?filepath,?String?content)?throws?IOException?{
        ????try?(BufferedOutputStream?bufferedOutputStream?=?new?BufferedOutputStream(
        ????????????new?FileOutputStream(filepath)))?{
        ????????bufferedOutputStream.write(content.getBytes());
        ????}
        }

        方法 6:Files

        接下來(lái)的操作方法和之前的代碼都不同,接下來(lái)咱們就使用 JDK 7 中提供的一個(gè)新的文件操作類(lèi) Files?來(lái)實(shí)現(xiàn)文件的寫(xiě)入。

        Files?類(lèi)是 JDK 7 添加的新的操作文件的類(lèi),它提供了提供了大量處理文件的方法,例如文件復(fù)制、讀取、寫(xiě)入,獲取文件屬性、快捷遍歷文件目錄等,這些方法極大的方便了文件的操作,它的實(shí)現(xiàn)代碼如下:

        /**
        ?*?方法 6:使用 Files 寫(xiě)文件
        ?*?@param?filepath?文件目錄
        ?*?@param?content??待寫(xiě)入內(nèi)容
        ?*?@throws?IOException
        ?*/

        public?static?void?filesTest(String?filepath,?String?content)?throws?IOException?{
        ????Files.write(Paths.get(filepath),?content.getBytes());
        }

        以上這些方法都可以實(shí)現(xiàn)文件的寫(xiě)入,那哪一種方法性能更高呢?接下來(lái)我們來(lái)測(cè)試一下。

        5.性能測(cè)試

        我們先來(lái)構(gòu)建一個(gè)比較大的字符串,然后分別用以上 6 種方法來(lái)測(cè)試文件寫(xiě)入的速度,最后再把結(jié)果打印出來(lái),測(cè)試代碼如下:

        import?java.io.*;
        import?java.nio.file.Files;
        import?java.nio.file.Paths;

        public?class?WriteExample?{
        ????public?static?void?main(String[]?args)?throws?IOException?{
        ????????//?構(gòu)建寫(xiě)入內(nèi)容
        ????????StringBuilder?stringBuilder?=?new?StringBuilder();
        ????????for?(int?i?=?0;?i?1000000;?i++)?{
        ????????????stringBuilder.append("ABCDEFGHIGKLMNOPQRSEUVWXYZ");
        ????????}
        ????????//?寫(xiě)入內(nèi)容
        ????????final?String?content?=?stringBuilder.toString();
        ????????//?存放文件的目錄
        ????????final?String?filepath1?=?"/Users/mac/Downloads/io_test/write1.txt";
        ????????final?String?filepath2?=?"/Users/mac/Downloads/io_test/write2.txt";
        ????????final?String?filepath3?=?"/Users/mac/Downloads/io_test/write3.txt";
        ????????final?String?filepath4?=?"/Users/mac/Downloads/io_test/write4.txt";
        ????????final?String?filepath5?=?"/Users/mac/Downloads/io_test/write5.txt";
        ????????final?String?filepath6?=?"/Users/mac/Downloads/io_test/write6.txt";

        ????????//?方法一:使用?FileWriter?寫(xiě)文件
        ????????long?stime1?=?System.currentTimeMillis();
        ????????fileWriterTest(filepath1,?content);
        ????????long?etime1?=?System.currentTimeMillis();
        ????????System.out.println("FileWriter?寫(xiě)入用時(shí):"?+?(etime1?-?stime1));

        ????????//?方法二:使用?BufferedWriter?寫(xiě)文件
        ????????long?stime2?=?System.currentTimeMillis();
        ????????bufferedWriterTest(filepath2,?content);
        ????????long?etime2?=?System.currentTimeMillis();
        ????????System.out.println("BufferedWriter?寫(xiě)入用時(shí):"?+?(etime2?-?stime2));

        ????????//?方法三:使用?PrintWriter?寫(xiě)文件
        ????????long?stime3?=?System.currentTimeMillis();
        ????????printWriterTest(filepath3,?content);
        ????????long?etime3?=?System.currentTimeMillis();
        ????????System.out.println("PrintWriterTest?寫(xiě)入用時(shí):"?+?(etime3?-?stime3));

        ????????//?方法四:使用?FileOutputStream??寫(xiě)文件
        ????????long?stime4?=?System.currentTimeMillis();
        ????????fileOutputStreamTest(filepath4,?content);
        ????????long?etime4?=?System.currentTimeMillis();
        ????????System.out.println("FileOutputStream?寫(xiě)入用時(shí):"?+?(etime4?-?stime4));

        ????????//?方法五:使用?BufferedOutputStream?寫(xiě)文件
        ????????long?stime5?=?System.currentTimeMillis();
        ????????bufferedOutputStreamTest(filepath5,?content);
        ????????long?etime5?=?System.currentTimeMillis();
        ????????System.out.println("BufferedOutputStream?寫(xiě)入用時(shí):"?+?(etime5?-?stime5));

        ????????//?方法六:使用?Files?寫(xiě)文件
        ????????long?stime6?=?System.currentTimeMillis();
        ????????filesTest(filepath6,?content);
        ????????long?etime6?=?System.currentTimeMillis();
        ????????System.out.println("Files?寫(xiě)入用時(shí):"?+?(etime6?-?stime6));

        ????}

        ????/**
        ?????*?方法六:使用?Files?寫(xiě)文件
        ?????*?@param?filepath?文件目錄
        ?????*?@param?content??待寫(xiě)入內(nèi)容
        ?????*?@throws?IOException
        ?????*/

        ????private?static?void?filesTest(String?filepath,?String?content)?throws?IOException?{
        ????????Files.write(Paths.get(filepath),?content.getBytes());
        ????}

        ????/**
        ?????*?方法五:使用?BufferedOutputStream?寫(xiě)文件
        ?????*?@param?filepath?文件目錄
        ?????*?@param?content??待寫(xiě)入內(nèi)容
        ?????*?@throws?IOException
        ?????*/

        ????private?static?void?bufferedOutputStreamTest(String?filepath,?String?content)?throws?IOException?{
        ????????try?(BufferedOutputStream?bufferedOutputStream?=?new?BufferedOutputStream(
        ????????????????new?FileOutputStream(filepath)))?{
        ????????????bufferedOutputStream.write(content.getBytes());
        ????????}
        ????}

        ????/**
        ?????*?方法四:使用?FileOutputStream??寫(xiě)文件
        ?????*?@param?filepath?文件目錄
        ?????*?@param?content??待寫(xiě)入內(nèi)容
        ?????*?@throws?IOException
        ?????*/

        ????private?static?void?fileOutputStreamTest(String?filepath,?String?content)?throws?IOException?{
        ????????try?(FileOutputStream?fileOutputStream?=?new?FileOutputStream(filepath))?{
        ????????????byte[]?bytes?=?content.getBytes();
        ????????????fileOutputStream.write(bytes);
        ????????}
        ????}

        ????/**
        ?????*?方法三:使用?PrintWriter?寫(xiě)文件
        ?????*?@param?filepath?文件目錄
        ?????*?@param?content??待寫(xiě)入內(nèi)容
        ?????*?@throws?IOException
        ?????*/

        ????private?static?void?printWriterTest(String?filepath,?String?content)?throws?IOException?{
        ????????try?(PrintWriter?printWriter?=?new?PrintWriter(new?FileWriter(filepath)))?{
        ????????????printWriter.print(content);
        ????????}
        ????}

        ????/**
        ?????*?方法二:使用?BufferedWriter?寫(xiě)文件
        ?????*?@param?filepath?文件目錄
        ?????*?@param?content??待寫(xiě)入內(nèi)容
        ?????*?@throws?IOException
        ?????*/

        ????private?static?void?bufferedWriterTest(String?filepath,?String?content)?throws?IOException?{
        ????????try?(BufferedWriter?bufferedWriter?=?new?BufferedWriter(new?FileWriter(filepath)))?{
        ????????????bufferedWriter.write(content);
        ????????}
        ????}

        ????/**
        ?????*?方法一:使用?FileWriter?寫(xiě)文件
        ?????*?@param?filepath?文件目錄
        ?????*?@param?content??待寫(xiě)入內(nèi)容
        ?????*?@throws?IOException
        ?????*/

        ????private?static?void?fileWriterTest(String?filepath,?String?content)?throws?IOException?{
        ????????try?(FileWriter?fileWriter?=?new?FileWriter(filepath))?{
        ????????????fileWriter.append(content);
        ????????}
        ????}
        }

        在查看結(jié)果之前,我們先去對(duì)應(yīng)的文件夾看看寫(xiě)入的文件是否正常,如下圖所示:

        從上述結(jié)果可以看出,每種方法都正常寫(xiě)入了 26 MB 的數(shù)據(jù),它們最終執(zhí)行的結(jié)果如下圖所示:

        從以上結(jié)果可以看出,字符流的操作速度最快,這是因?yàn)槲覀儽敬螠y(cè)試的代碼操作的是字符串,所以在使用字節(jié)流時(shí),需要先將字符串轉(zhuǎn)換為字節(jié)流,因此在執(zhí)行效率上不占優(yōu)勢(shì)。

        從上述結(jié)果可以看出,性能最好的是帶有緩沖區(qū)的字符串寫(xiě)入流 BufferedWriter,性能最慢的是 Files。

        PS:以上的測(cè)試結(jié)果只是針對(duì)字符串的操作場(chǎng)景有效,如果操作的是二進(jìn)制的文件,那么就應(yīng)該使用帶緩沖區(qū)的字節(jié)流 BufferedOutputStream。

        6.擴(kuò)展知識(shí):內(nèi)容追加

        以上代碼會(huì)對(duì)文件進(jìn)行重寫(xiě),如果只想在原有的基礎(chǔ)上追加內(nèi)容,就需要在創(chuàng)建寫(xiě)入流的時(shí)候多設(shè)置一個(gè) append?的參數(shù)為 true,比如如果我們使用 FileWriter?來(lái)實(shí)現(xiàn)文件的追加的話,實(shí)現(xiàn)代碼是這樣的:

        public?static?void?fileWriterMethod(String?filepath,?String?content)?throws?IOException?{
        ????//?第二個(gè)?append?的參數(shù)傳遞一個(gè)?true?=?追加文件的意思
        ????try?(FileWriter?fileWriter?=?new?FileWriter(filepath,?true))?{
        ????????fileWriter.append(content);
        ????}
        }

        如果使用的是 BufferedWriter?或 PrintWriter,也是需要在構(gòu)建 new FileWriter?類(lèi)時(shí)多設(shè)置一個(gè) append 的參數(shù)為 true,實(shí)現(xiàn)代碼如下:

        try?(BufferedWriter?bufferedWriter?=?new?BufferedWriter(
        ????new?FileWriter(filepath,?true)))?{
        ????bufferedWriter.write(content);
        }

        相比來(lái)說(shuō) Files 類(lèi)要想實(shí)現(xiàn)文件的追加寫(xiě)法更加特殊一些,它需要在調(diào)用 write 方法時(shí)多傳一個(gè) StandardOpenOption.APPEND 的參數(shù),它的實(shí)現(xiàn)代碼如下:

        Files.write(Paths.get(filepath),?content.getBytes(),?StandardOpenOption.APPEND);

        7.總結(jié)

        本文我們展示了 6 種寫(xiě)入文件的方法,這 6 種方法總共分為 3 類(lèi):字符流寫(xiě)入、字節(jié)流寫(xiě)入和 Files?類(lèi)寫(xiě)入。其中操作最便利的是 Files 類(lèi),但它的性能不怎么好。如果對(duì)性能有要求就推薦使用帶有緩存區(qū)的流來(lái)完成操作,如 BufferedWriter?或 BufferedOutputStream。如果寫(xiě)入的內(nèi)容是字符串的話,那么推薦使用 BufferedWriter,如果寫(xiě)入的內(nèi)容是二進(jìn)制文件的話就推薦使用 BufferedOutputStream

        參考 & 鳴謝

        https://www.cnblogs.com/absfree/p/5415092.html


        往期推薦

        線程池的7種創(chuàng)建方式,強(qiáng)烈推薦你用它...


        求求你,別再用wait和notify了!


        硬核Redis總結(jié),看這篇就夠了!


        關(guān)注我,每天陪你進(jìn)步一點(diǎn)點(diǎn)!

        瀏覽 26
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美精品久久久久久久久久 | 三级黄色软件下载 | 色湿淫 | 天天色五月 | 成年M M视频 | 午夜国产偷拍 | 欧美激情一区二区 | 亚洲自拍中文 | 古装一级无遮挡A片 | 欧美精品欧美 |