自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

文件寫入的6種方法,你覺得哪種性能最好?

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

[[359184]]

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

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

0.什么是流?

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

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

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

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

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


其中 InputStream 用于讀操作,而 OutputStream 用于寫操作。

2.什么是字符流?

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

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


3.流的分類

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

① 按流向分類

  • 輸出流:OutputStream 和 Writer 為基類。
  • 輸入流:InputStream 和 Reader 為基類。

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

  • 字節(jié)流:OutputStream 和 InputStream 為基類。
  • 字符流:Writer 和 Reader 為基類。

③ 根據(jù)功能分類

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

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

4.寫文件的6種方法

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


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

方法 1:FileWriter

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


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

  1. /** 
  2.   * 方法 1:使用 FileWriter 寫文件 
  3.   * @param filepath 文件目錄 
  4.   * @param content  待寫入內(nèi)容 
  5.   * @throws IOException 
  6.   */ 
  7. public static void fileWriterMethod(String filepath, String content) throws IOException { 
  8.     try (FileWriter fileWriter = new FileWriter(filepath)) { 
  9.         fileWriter.append(content); 
  10.     } 

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

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

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


  • 關(guā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ū),因此它寫入文件的性能更高(下文會(huì)對(duì)二者進(jìn)行測(cè)試)。

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

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

緩沖區(qū)的優(yōu)勢(shì)

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

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

  1. /** 
  2.  * 方法 2:使用 BufferedWriter 寫文件 
  3.  * @param filepath 文件目錄 
  4.  * @param content  待寫入內(nèi)容 
  5.  * @throws IOException 
  6.  */ 
  7. public static void bufferedWriterMethod(String filepath, String content) throws IOException { 
  8.     try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filepath))) { 
  9.         bufferedWriter.write(content); 
  10.     } 

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

方法 3:PrintWriter

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

  1. /** 
  2.  * 方法 3:使用 PrintWriter 寫文件 
  3.  * @param filepath 文件目錄 
  4.  * @param content  待寫入內(nèi)容 
  5.  * @throws IOException 
  6.  */ 
  7. public static void printWriterMethod(String filepath, String content) throws IOException { 
  8.     try (PrintWriter printWriter = new PrintWriter(new FileWriter(filepath))) { 
  9.         printWriter.print(content); 
  10.     } 

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

方法 4:FileOutputStream

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

  1. /** 
  2.  * 方法 4:使用 FileOutputStream 寫文件 
  3.  * @param filepath 文件目錄 
  4.  * @param content  待寫入內(nèi)容 
  5.  * @throws IOException 
  6.  */ 
  7. public static void fileOutputStreamMethod(String filepath, String content) throws IOException { 
  8.     try (FileOutputStream fileOutputStream = new FileOutputStream(filepath)) { 
  9.         byte[] bytes = content.getBytes(); 
  10.         fileOutputStream.write(bytes); 
  11.     } 

方法 5:BufferedOutputStream

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

  1. /** 
  2.  * 方法 5:使用 BufferedOutputStream 寫文件 
  3.  * @param filepath 文件目錄 
  4.  * @param content  待寫入內(nèi)容 
  5.  * @throws IOException 
  6.  */ 
  7. public static void bufferedOutputStreamMethod(String filepath, String content) throws IOException { 
  8.     try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( 
  9.             new FileOutputStream(filepath))) { 
  10.         bufferedOutputStream.write(content.getBytes()); 
  11.     } 

方法 6:Files

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

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

  1. /** 
  2.  * 方法 6:使用 Files 寫文件 
  3.  * @param filepath 文件目錄 
  4.  * @param content  待寫入內(nèi)容 
  5.  * @throws IOException 
  6.  */ 
  7. public static void filesTest(String filepath, String content) throws IOException { 
  8.     Files.write(Paths.get(filepath), content.getBytes()); 

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

5.性能測(cè)試

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

  1. import java.io.*; 
  2. import java.nio.file.Files; 
  3. import java.nio.file.Paths; 
  4.  
  5. public class WriteExample { 
  6.     public static void main(String[] args) throws IOException { 
  7.         // 構(gòu)建寫入內(nèi)容 
  8.         StringBuilder stringBuilder = new StringBuilder(); 
  9.         for (int i = 0; i < 1000000; i++) { 
  10.             stringBuilder.append("ABCDEFGHIGKLMNOPQRSEUVWXYZ"); 
  11.         } 
  12.         // 寫入內(nèi)容 
  13.         final String content = stringBuilder.toString(); 
  14.         // 存放文件的目錄 
  15.         final String filepath1 = "/Users/mac/Downloads/io_test/write1.txt"
  16.         final String filepath2 = "/Users/mac/Downloads/io_test/write2.txt"
  17.         final String filepath3 = "/Users/mac/Downloads/io_test/write3.txt"
  18.         final String filepath4 = "/Users/mac/Downloads/io_test/write4.txt"
  19.         final String filepath5 = "/Users/mac/Downloads/io_test/write5.txt"
  20.         final String filepath6 = "/Users/mac/Downloads/io_test/write6.txt"
  21.  
  22.         // 方法一:使用 FileWriter 寫文件 
  23.         long stime1 = System.currentTimeMillis(); 
  24.         fileWriterTest(filepath1, content); 
  25.         long etime1 = System.currentTimeMillis(); 
  26.         System.out.println("FileWriter 寫入用時(shí):" + (etime1 - stime1)); 
  27.  
  28.         // 方法二:使用 BufferedWriter 寫文件 
  29.         long stime2 = System.currentTimeMillis(); 
  30.         bufferedWriterTest(filepath2, content); 
  31.         long etime2 = System.currentTimeMillis(); 
  32.         System.out.println("BufferedWriter 寫入用時(shí):" + (etime2 - stime2)); 
  33.  
  34.         // 方法三:使用 PrintWriter 寫文件 
  35.         long stime3 = System.currentTimeMillis(); 
  36.         printWriterTest(filepath3, content); 
  37.         long etime3 = System.currentTimeMillis(); 
  38.         System.out.println("PrintWriterTest 寫入用時(shí):" + (etime3 - stime3)); 
  39.  
  40.         // 方法四:使用 FileOutputStream  寫文件 
  41.         long stime4 = System.currentTimeMillis(); 
  42.         fileOutputStreamTest(filepath4, content); 
  43.         long etime4 = System.currentTimeMillis(); 
  44.         System.out.println("FileOutputStream 寫入用時(shí):" + (etime4 - stime4)); 
  45.  
  46.         // 方法五:使用 BufferedOutputStream 寫文件 
  47.         long stime5 = System.currentTimeMillis(); 
  48.         bufferedOutputStreamTest(filepath5, content); 
  49.         long etime5 = System.currentTimeMillis(); 
  50.         System.out.println("BufferedOutputStream 寫入用時(shí):" + (etime5 - stime5)); 
  51.  
  52.         // 方法六:使用 Files 寫文件 
  53.         long stime6 = System.currentTimeMillis(); 
  54.         filesTest(filepath6, content); 
  55.         long etime6 = System.currentTimeMillis(); 
  56.         System.out.println("Files 寫入用時(shí):" + (etime6 - stime6)); 
  57.  
  58.     } 
  59.  
  60.     /** 
  61.      * 方法六:使用 Files 寫文件 
  62.      * @param filepath 文件目錄 
  63.      * @param content  待寫入內(nèi)容 
  64.      * @throws IOException 
  65.      */ 
  66.     private static void filesTest(String filepath, String content) throws IOException { 
  67.         Files.write(Paths.get(filepath), content.getBytes()); 
  68.     } 
  69.  
  70.     /** 
  71.      * 方法五:使用 BufferedOutputStream 寫文件 
  72.      * @param filepath 文件目錄 
  73.      * @param content  待寫入內(nèi)容 
  74.      * @throws IOException 
  75.      */ 
  76.     private static void bufferedOutputStreamTest(String filepath, String content) throws IOException { 
  77.         try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( 
  78.                 new FileOutputStream(filepath))) { 
  79.             bufferedOutputStream.write(content.getBytes()); 
  80.         } 
  81.     } 
  82.  
  83.     /** 
  84.      * 方法四:使用 FileOutputStream  寫文件 
  85.      * @param filepath 文件目錄 
  86.      * @param content  待寫入內(nèi)容 
  87.      * @throws IOException 
  88.      */ 
  89.     private static void fileOutputStreamTest(String filepath, String content) throws IOException { 
  90.         try (FileOutputStream fileOutputStream = new FileOutputStream(filepath)) { 
  91.             byte[] bytes = content.getBytes(); 
  92.             fileOutputStream.write(bytes); 
  93.         } 
  94.     } 
  95.  
  96.     /** 
  97.      * 方法三:使用 PrintWriter 寫文件 
  98.      * @param filepath 文件目錄 
  99.      * @param content  待寫入內(nèi)容 
  100.      * @throws IOException 
  101.      */ 
  102.     private static void printWriterTest(String filepath, String content) throws IOException { 
  103.         try (PrintWriter printWriter = new PrintWriter(new FileWriter(filepath))) { 
  104.             printWriter.print(content); 
  105.         } 
  106.     } 
  107.  
  108.     /** 
  109.      * 方法二:使用 BufferedWriter 寫文件 
  110.      * @param filepath 文件目錄 
  111.      * @param content  待寫入內(nèi)容 
  112.      * @throws IOException 
  113.      */ 
  114.     private static void bufferedWriterTest(String filepath, String content) throws IOException { 
  115.         try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filepath))) { 
  116.             bufferedWriter.write(content); 
  117.         } 
  118.     } 
  119.  
  120.     /** 
  121.      * 方法一:使用 FileWriter 寫文件 
  122.      * @param filepath 文件目錄 
  123.      * @param content  待寫入內(nèi)容 
  124.      * @throws IOException 
  125.      */ 
  126.     private static void fileWriterTest(String filepath, String content) throws IOException { 
  127.         try (FileWriter fileWriter = new FileWriter(filepath)) { 
  128.             fileWriter.append(content); 
  129.         } 
  130.     } 

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


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


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

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

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

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

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

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

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

  1. try (BufferedWriter bufferedWriter = new BufferedWriter( 
  2.     new FileWriter(filepath, true))) { 
  3.     bufferedWriter.write(content); 

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

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

7.總結(jié)

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

參考 & 鳴謝

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

 

責(zé)任編輯:姜華 來源: Java中文社群
相關(guān)推薦

2024-01-31 09:24:58

2023-10-30 09:46:08

接口重試技巧

2025-02-04 09:35:11

PHP開發(fā)者JSON

2010-04-30 10:40:19

Oracle實(shí)現(xiàn)

2010-08-26 09:16:42

Web服務(wù)器

2020-06-23 09:52:31

運(yùn)營效率首席信息官IT預(yù)算

2023-09-07 15:11:44

2020-05-15 10:09:17

優(yōu)化創(chuàng)新數(shù)字化轉(zhuǎn)型CIO

2024-07-30 10:45:05

容器化編排工具

2015-03-13 13:50:47

Java讀取文件夾大小Java讀取文件Java讀取

2024-12-23 08:10:00

Python代碼性能代碼

2010-07-21 16:01:33

升職

2009-04-01 11:17:52

內(nèi)存分配調(diào)優(yōu)Oracle

2014-11-04 09:57:30

Windows

2013-10-21 14:26:04

2009-04-07 10:52:00

職場工作方法

2019-08-22 09:57:10

ExpdpImpdpMOS

2017-11-03 10:40:25

Python復(fù)制文件方法

2018-01-04 09:46:48

PHPHTTP

2022-11-10 14:33:40

Kubernetes容器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)