衆所周知,Java中有多種針對檔案的操作類,以面向位元組流和字元流可分為兩大類,這裡以寫入為例:
面向位元組流的:FileOutputStream和BufferedOutputStream
面向字元流的:FileWriter和BufferedWriter
近年來發展出New I/O ,也叫NIO,裡面又包裝了兩個類:NewOutputStream 和 NewBufferedWriter。現在,我們建立測試程式,比較這些類寫入檔案的性能。
機器配置
Processor Name: Intel Core i7
Processor Speed: 2.2 GHz
Number of Processors: 1
Total Number of Cores: 4
L2 Cache (per Core): 256 KB
L3 Cache: 6 MB
Memory: 16 GB
測試程式
縱向比較:幾種檔案操作類向檔案中寫入相同行數的内容(每行内容均為“寫入檔案Data\n”),比較其耗費時間。
橫向比較:對于同一個檔案操作類,比較寫入不同行數内容情況下所耗費時間;本文以2的次方指數級增長行數。
import java.io.File;
import java.io.FileOutputStream;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
public class testFileIO {
public static void testDriver () throws IOException {
int maxlineNum = 100000001;//寫入檔案的最大行數
int startlineNum = 1;//寫入檔案的行數
int Multiplying = 2;//行數增長倍率
long begin = 0L;
long end = 0L;
//将時間統計寫入檔案Result.txt中
FileWriter fileWriter = new FileWriter("./Result.txt", true);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
System.out.println("Test FileOutputStream begin.");
for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
begin = System.currentTimeMillis();
testFileOutputStream(lineNum);
end = System.currentTimeMillis();
long timeElapse_FileOutputStream = end - begin;
bufferedWriter.write(String.valueOf(timeElapse_FileOutputStream)+"\t");
}
System.out.println("Test FileOutputStream end.\n");
System.out.println("Test BufferedOutputStream begin.");
bufferedWriter.write("\n");
for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
begin = System.currentTimeMillis();
testBufferedOutputStream(lineNum);
end = System.currentTimeMillis();
long timeElapse_BufferedOutputStream = end - begin;
bufferedWriter.write(String.valueOf(timeElapse_BufferedOutputStream)+"\t");
}
System.out.println("Test BufferedOutputStream end.\n");
System.out.println("Test FileWriter begin.");
bufferedWriter.write("\n");
for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
begin = System.currentTimeMillis();
testFileWriter(lineNum);
end = System.currentTimeMillis();
long timeElapse_FileWriter = end - begin;
bufferedWriter.write(String.valueOf(timeElapse_FileWriter)+"\t");
}
System.out.println("Test FileWriter end.\n");
System.out.println("Test BufferedWriter begin.");
bufferedWriter.write("\n");
for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
begin = System.currentTimeMillis();
testBufferedWriter(lineNum);
end = System.currentTimeMillis();
long timeElapse_BufferedWriter = end - begin;
bufferedWriter.write(String.valueOf(timeElapse_BufferedWriter)+"\t");
}
System.out.println("Test BufferedWriter end.\n");
System.out.println("Test NewOutputStream begin.");
bufferedWriter.write("\n");
for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
begin = System.currentTimeMillis();
testNewOutputStream(lineNum);
end = System.currentTimeMillis();
long timeElapse_NewOutputStream = end - begin;
bufferedWriter.write(String.valueOf(timeElapse_NewOutputStream)+"\t");
}
System.out.println("Test NewOutputStream end.\n");
System.out.println("Test NewBufferedWriter begin.");
bufferedWriter.write("\n");
for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
begin = System.currentTimeMillis();
testNewBufferedWriter(lineNum);
end = System.currentTimeMillis();
long timeElapse_NewBufferedWriter = end - begin;
bufferedWriter.write(String.valueOf(timeElapse_NewBufferedWriter)+"\t");
}
System.out.println("Test NewOutputStream end.\n");
bufferedWriter.close();
}
/************************** I/O *****************************/
//面向位元組
public static void testFileOutputStream (int lineNum) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream(new File("./testFileOutputStream.txt"));
while (--lineNum > 0) {
fileOutputStream.write("寫入檔案Data\n".getBytes());
}
fileOutputStream.close();
}
public static void testBufferedOutputStream (int lineNum) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream(new File("./testBufferedOutputStream.txt"));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
while (--lineNum > 0) {
bufferedOutputStream.write("寫入檔案Data\n".getBytes());
}
bufferedOutputStream.close();
}
//面向字元
public static void testFileWriter (int lineNum) throws IOException {
FileWriter fileWriter = new FileWriter("./testFileWriter.txt");
while (--lineNum > 0) {
fileWriter.write("寫入檔案Data\n");
}
fileWriter.close();
}
public static void testBufferedWriter (int lineNum) throws IOException {
FileWriter fileWriter = new FileWriter("./testBufferedWriter.txt");
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
while (--lineNum > 0) {
bufferedWriter.write("寫入檔案Data\n");
}
bufferedWriter.close();
}
/************************** NIO ****************************/
public static void testNewOutputStream (int lineNum) throws IOException {
OutputStream outputStream = Files.newOutputStream(Paths.get("./testNewOutputStream.txt"));
while (--lineNum > 0) {
outputStream.write("寫入檔案Data\n".getBytes());
}
outputStream.close();
}
public static void testNewBufferedWriter (int lineNum) throws IOException {
BufferedWriter newBufferedReader = Files.newBufferedWriter(Paths.get("./testNewBufferedWriter.txt"));
while (--lineNum > 0) {
newBufferedReader.write("寫入檔案Data\n");
}
newBufferedReader.close();
}
public static void main (String[] args) throws IOException {
//多次測試時可清空result.txt檔案
FileWriter fileWriter = new FileWriter("./Result.txt");
testDriver();
}
}
測試結果,如圖所示:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cGcq5CO3QTOzkjZhlzN3gTNjVDMyYzX1EDMxATMxEzLcJDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.jpg)
從上圖可以看出,寫入行數超過20W以上時,FileOutputStream和NewOutputStream耗費時間遠遠超出其他4個類。為了清晰,讓我們放大其他4個類的圖:
可以看出,這4個類中,BufferWriter和NewBufferedWriter所耗費時間更少,但總體差别不是很大。
讓我們再來看看,寫入26W行資料以下時的情況:
可以看出,在資料量較小的情況下,這4個類所耗費時間的差異并不是很大,在更小的資料量下,它們的效率幾乎沒有差别。
後記
從以上分析可知(注意橫坐标寫入行數是指數級增加的),各個類的時間複雜度大緻為O(k),其中不同的類的k不同,導緻了最終巨大的差異。
這裡隻給出了測試結果,并未分析其底層實作原理,歡迎評論區留言。
另外,我沒有在其他機器測試,有興趣的小夥伴可以将自己的測試結果發出來,共同進步^_^。
附件
本次測試資料結果(若看不清,可以将浏覽器字型放大,或下載下傳到本地看)
~~~~~~~~~~分割線~~~~~~~~~
評論區小夥伴“ andorxor”提出:
XXXOutputStream是用來寫二進制的,你把字元串轉換成位元組數組再寫自然就慢了,主要慢在轉換的過程。
是以,将程式修改,提前把字元和位元組内容都準備好,再次驗證。新程式如下:
import java.io.File;
import java.io.FileOutputStream;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
public class testFileIO {
public static void testDriver () throws IOException {
int maxlineNum = 100000001;//寫入檔案的最大行數
int startlineNum = 1;//寫入檔案的行數
int Multiplying = 2;//行數增長倍率
String contentChars = "寫入檔案Data\n";//每行的内容(字元流)
byte[] contentBytes = "寫入檔案Data\n".getBytes();//每行的内容(位元組流)
long begin = 0L;
long end = 0L;
//将時間統計寫入檔案Result.txt中
FileWriter fileWriter = new FileWriter("./Result.txt", true);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
System.out.println("Test FileOutputStream begin.");
for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
begin = System.currentTimeMillis();
testFileOutputStream(lineNum,contentBytes);
end = System.currentTimeMillis();
long timeElapse_FileOutputStream = end - begin;
bufferedWriter.write(String.valueOf(timeElapse_FileOutputStream)+"\t");
}
System.out.println("Test FileOutputStream end.\n");
System.out.println("Test BufferedOutputStream begin.");
bufferedWriter.write("\n");
for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
begin = System.currentTimeMillis();
testBufferedOutputStream(lineNum,contentBytes);
end = System.currentTimeMillis();
long timeElapse_BufferedOutputStream = end - begin;
bufferedWriter.write(String.valueOf(timeElapse_BufferedOutputStream)+"\t");
}
System.out.println("Test BufferedOutputStream end.\n");
System.out.println("Test FileWriter begin.");
bufferedWriter.write("\n");
for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
begin = System.currentTimeMillis();
testFileWriter(lineNum,contentChars);
end = System.currentTimeMillis();
long timeElapse_FileWriter = end - begin;
bufferedWriter.write(String.valueOf(timeElapse_FileWriter)+"\t");
}
System.out.println("Test FileWriter end.\n");
System.out.println("Test BufferedWriter begin.");
bufferedWriter.write("\n");
for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
begin = System.currentTimeMillis();
testBufferedWriter(lineNum,contentChars);
end = System.currentTimeMillis();
long timeElapse_BufferedWriter = end - begin;
bufferedWriter.write(String.valueOf(timeElapse_BufferedWriter)+"\t");
}
System.out.println("Test BufferedWriter end.\n");
System.out.println("Test NewOutputStream begin.");
bufferedWriter.write("\n");
for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
begin = System.currentTimeMillis();
testNewOutputStream(lineNum,contentBytes);
end = System.currentTimeMillis();
long timeElapse_NewOutputStream = end - begin;
bufferedWriter.write(String.valueOf(timeElapse_NewOutputStream)+"\t");
}
System.out.println("Test NewOutputStream end.\n");
System.out.println("Test NewBufferedWriter begin.");
bufferedWriter.write("\n");
for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
begin = System.currentTimeMillis();
testNewBufferedWriter(lineNum,contentChars);
end = System.currentTimeMillis();
long timeElapse_NewBufferedWriter = end - begin;
bufferedWriter.write(String.valueOf(timeElapse_NewBufferedWriter)+"\t");
}
System.out.println("Test NewOutputStream end.\n");
bufferedWriter.close();
}
/************************** I/O *****************************/
//面向位元組
public static void testFileOutputStream (int lineNum, byte[] content) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream(new File("./testFileOutputStream.txt"));
while (--lineNum > 0) {
fileOutputStream.write(content);
}
fileOutputStream.close();
}
public static void testBufferedOutputStream (int lineNum, byte[] content) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream(new File("./testBufferedOutputStream.txt"));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
while (--lineNum > 0) {
bufferedOutputStream.write(content);
}
bufferedOutputStream.close();
}
//面向字元
public static void testFileWriter (int lineNum, String content) throws IOException {
FileWriter fileWriter = new FileWriter("./testFileWriter.txt");
while (--lineNum > 0) {
fileWriter.write(content);
}
fileWriter.close();
}
public static void testBufferedWriter (int lineNum, String content) throws IOException {
FileWriter fileWriter = new FileWriter("./testBufferedWriter.txt");
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
while (--lineNum > 0) {
bufferedWriter.write(content);
}
bufferedWriter.close();
}
/************************** NIO ****************************/
public static void testNewOutputStream (int lineNum, byte[] content) throws IOException {
OutputStream outputStream = Files.newOutputStream(Paths.get("./testNewOutputStream.txt"));
while (--lineNum > 0) {
outputStream.write(content);
}
outputStream.close();
}
public static void testNewBufferedWriter (int lineNum,String content) throws IOException {
BufferedWriter newBufferedReader = Files.newBufferedWriter(Paths.get("./testNewBufferedWriter.txt"));
while (--lineNum > 0) {
newBufferedReader.write(content);
}
newBufferedReader.close();
}
public static void main (String[] args) throws IOException {
//多次測試時可清空result.txt檔案
FileWriter fileWriter = new FileWriter("./Result.txt");
testDriver();
}
}
結果如圖所示:
可以看出和前面的案例幾乎沒有差異(圖就不畫了)。
是以XXXOutputStream效率低的原因并不是字元串轉換成位元組數組,而是其本身的實作方式所緻。
作者:xiaoxi666