Java文件操作类效率对比

Posted xiaoxi666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java文件操作类效率对比相关的知识,希望对你有一定的参考价值。

前言

众所周知,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的次方指数级增长行数

  1 import java.io.File;
  2 import java.io.FileOutputStream;
  3 import java.io.*;
  4 import java.nio.file.Files;
  5 import java.nio.file.Paths;
  6 
  7 public class testFileIO {
  8 
  9     public static void testDriver () throws IOException {
 10         int maxlineNum = 100000001;//写入文件的最大行数
 11         int startlineNum = 1;//写入文件的行数
 12         int Multiplying = 2;//行数增长倍率
 13 
 14         long begin = 0L;
 15         long end = 0L;
 16 
 17         //将时间统计写入文件Result.txt中
 18         FileWriter fileWriter = new FileWriter("./Result.txt", true);
 19         BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
 20 
 21         System.out.println("Test FileOutputStream begin.");
 22         for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
 23             begin = System.currentTimeMillis();
 24             testFileOutputStream(lineNum);
 25             end = System.currentTimeMillis();
 26             long timeElapse_FileOutputStream = end - begin;
 27             bufferedWriter.write(String.valueOf(timeElapse_FileOutputStream)+"\\t");
 28         }
 29         System.out.println("Test FileOutputStream end.\\n");
 30 
 31         System.out.println("Test BufferedOutputStream begin.");
 32         bufferedWriter.write("\\n");
 33         for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
 34             begin = System.currentTimeMillis();
 35             testBufferedOutputStream(lineNum);
 36             end = System.currentTimeMillis();
 37             long timeElapse_BufferedOutputStream = end - begin;
 38             bufferedWriter.write(String.valueOf(timeElapse_BufferedOutputStream)+"\\t");
 39         }
 40         System.out.println("Test BufferedOutputStream end.\\n");
 41 
 42         System.out.println("Test FileWriter begin.");
 43         bufferedWriter.write("\\n");
 44         for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
 45             begin = System.currentTimeMillis();
 46             testFileWriter(lineNum);
 47             end = System.currentTimeMillis();
 48             long timeElapse_FileWriter = end - begin;
 49             bufferedWriter.write(String.valueOf(timeElapse_FileWriter)+"\\t");
 50         }
 51         System.out.println("Test FileWriter end.\\n");
 52 
 53         System.out.println("Test BufferedWriter begin.");
 54         bufferedWriter.write("\\n");
 55         for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
 56             begin = System.currentTimeMillis();
 57             testBufferedWriter(lineNum);
 58             end = System.currentTimeMillis();
 59             long timeElapse_BufferedWriter = end - begin;
 60             bufferedWriter.write(String.valueOf(timeElapse_BufferedWriter)+"\\t");
 61         }
 62         System.out.println("Test BufferedWriter end.\\n");
 63 
 64         System.out.println("Test NewOutputStream begin.");
 65         bufferedWriter.write("\\n");
 66         for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
 67             begin = System.currentTimeMillis();
 68             testNewOutputStream(lineNum);
 69             end = System.currentTimeMillis();
 70             long timeElapse_NewOutputStream = end - begin;
 71             bufferedWriter.write(String.valueOf(timeElapse_NewOutputStream)+"\\t");
 72         }
 73         System.out.println("Test NewOutputStream end.\\n");
 74 
 75         System.out.println("Test NewBufferedWriter begin.");
 76         bufferedWriter.write("\\n");
 77         for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
 78             begin = System.currentTimeMillis();
 79             testNewBufferedWriter(lineNum);
 80             end = System.currentTimeMillis();
 81             long timeElapse_NewBufferedWriter = end - begin;
 82             bufferedWriter.write(String.valueOf(timeElapse_NewBufferedWriter)+"\\t");
 83         }
 84         System.out.println("Test NewOutputStream end.\\n");
 85 
 86         bufferedWriter.close();
 87     }
 88 
 89     /************************** I/O *****************************/
 90     //面向字节
 91     public static void testFileOutputStream (int lineNum) throws IOException {
 92         FileOutputStream fileOutputStream = new FileOutputStream(new File("./testFileOutputStream.txt"));
 93         while (--lineNum > 0) {
 94             fileOutputStream.write("写入文件Data\\n".getBytes());
 95         }
 96         fileOutputStream.close();
 97     }
 98 
 99     public static void testBufferedOutputStream (int lineNum) throws IOException {
100         FileOutputStream fileOutputStream = new FileOutputStream(new File("./testBufferedOutputStream.txt"));
101         BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
102         while (--lineNum > 0) {
103             bufferedOutputStream.write("写入文件Data\\n".getBytes());
104         }
105         bufferedOutputStream.close();
106     }
107 
108     //面向字符
109     public static void testFileWriter (int lineNum) throws IOException {
110         FileWriter fileWriter = new FileWriter("./testFileWriter.txt");
111         while (--lineNum > 0) {
112             fileWriter.write("写入文件Data\\n");
113         }
114         fileWriter.close();
115     }
116 
117     public static void testBufferedWriter (int lineNum) throws IOException {
118         FileWriter fileWriter = new FileWriter("./testBufferedWriter.txt");
119         BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
120         while (--lineNum > 0) {
121             bufferedWriter.write("写入文件Data\\n");
122         }
123         bufferedWriter.close();
124     }
125 
126 
127     /************************** NIO ****************************/
128     public static void testNewOutputStream (int lineNum) throws IOException {
129         OutputStream outputStream = Files.newOutputStream(Paths.get("./testNewOutputStream.txt"));
130         while (--lineNum > 0) {
131             outputStream.write("写入文件Data\\n".getBytes());
132         }
133         outputStream.close();
134     }
135 
136     public static void testNewBufferedWriter (int lineNum) throws IOException {
137         BufferedWriter newBufferedReader = Files.newBufferedWriter(Paths.get("./testNewBufferedWriter.txt"));
138         while (--lineNum > 0) {
139             newBufferedReader.write("写入文件Data\\n");
140         }
141         newBufferedReader.close();
142     }
143 
144 
145     public static void main (String[] args) throws IOException {
146         //多次测试时可清空result.txt文件
147         FileWriter fileWriter = new FileWriter("./Result.txt");
148         testDriver();
149     }
150 }

测试结果

从上图可以看出,写入行数超过20W以上时,FileOutputStream和NewOutputStream耗费时间远远超出其他4个类。为了清晰,让我们放大其他4个类的图:

 

可以看出,这4个类中,BufferWriter和NewBufferedWriter所耗费时间更少,但总体差别不是很大。

让我们再来看看,写入26W行数据以下时的情况:

     

 可以看出,在数据量较小的情况下,这4个类所耗费时间的差异并不是很大,在更小的数据量下,它们的效率几乎没有差别。

后记

从以上分析可知(注意横坐标写入行数是指数级增加的),各个类的时间复杂度大致为O(k),其中不同的类的k不同,导致了最终巨大的差异。

这里只给出了测试结果,并未很深入地分析其底层实现原理,欢迎评论区留言。

另外,我没有在其他机器测试,有兴趣的小伙伴可以将自己的测试结果发出来,共同进步^_^

附件

本次测试数据结果(若看不清,可以将浏览器字体放大,或下载到本地看)

 

~~~~~~~~~~~~~~~~~~~~~分割线~~~~~~~~~~~~~~~~~~~~~~~

评论区小伙伴“ andorxor”提出:

XXXOutputStream是用来写二进制的,你把字符串转换成字节数组再写自然就慢了,主要慢在转换的过程。

因此,将程序修改,提前把字符和字节内容都准备好,再次验证。新程序如下:

  1 import java.io.File;
  2 import java.io.FileOutputStream;
  3 import java.io.*;
  4 import java.nio.file.Files;
  5 import java.nio.file.Paths;
  6 
  7 public class testFileIO {
  8 
  9 
 10     public static void testDriver () throws IOException {
 11         int maxlineNum = 100000001;//写入文件的最大行数
 12         int startlineNum = 1;//写入文件的行数
 13         int Multiplying = 2;//行数增长倍率
 14 
 15         String contentChars = "写入文件Data\\n";//每行的内容(字符流)
 16         byte[] contentBytes = "写入文件Data\\n".getBytes();//每行的内容(字节流)
 17 
 18         long begin = 0L;
 19         long end = 0L;
 20 
 21         //将时间统计写入文件Result.txt中
 22         FileWriter fileWriter = new FileWriter("./Result.txt", true);
 23         BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
 24 
 25         System.out.println("Test FileOutputStream begin.");
 26         for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
 27             begin = System.currentTimeMillis();
 28             testFileOutputStream(lineNum,contentBytes);
 29             end = System.currentTimeMillis();
 30             long timeElapse_FileOutputStream = end - begin;
 31             bufferedWriter.write(String.valueOf(timeElapse_FileOutputStream)+"\\t");
 32         }
 33         System.out.println("Test FileOutputStream end.\\n");
 34 
 35         System.out.println("Test BufferedOutputStream begin.");
 36         bufferedWriter.write("\\n");
 37         for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
 38             begin = System.currentTimeMillis();
 39             testBufferedOutputStream(lineNum,contentBytes);
 40             end = System.currentTimeMillis();
 41             long timeElapse_BufferedOutputStream = end - begin;
 42             bufferedWriter.write(String.valueOf(timeElapse_BufferedOutputStream)+"\\t");
 43         }
 44         System.out.println("Test BufferedOutputStream end.\\n");
 45 
 46         System.out.println("Test FileWriter begin.");
 47         bufferedWriter.write("\\n");
 48         for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
 49             begin = System.currentTimeMillis();
 50             testFileWriter(lineNum,contentChars);
 51             end = System.currentTimeMillis();
 52             long timeElapse_FileWriter = end - begin;
 53             bufferedWriter.write(String.valueOf(timeElapse_FileWriter)+"\\t");
 54         }
 55         System.out.println("Test FileWriter end.\\n");
 56 
 57         System.out.println("Test BufferedWriter begin.");
 58         bufferedWriter.write("\\n");
 59         for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
 60             begin = System.currentTimeMillis();
 61             testBufferedWriter(lineNum,contentChars);
 62             end = System.currentTimeMillis();
 63             long timeElapse_BufferedWriter = end - begin;
 64             bufferedWriter.write(String.valueOf(timeElapse_BufferedWriter)+"\\t");
 65         }
 66         System.out.println("Test BufferedWriter end.\\n");
 67 
 68         System.out.println("Test NewOutputStream begin.");
 69         bufferedWriter.write("\\n");
 70         for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
 71             begin = System.currentTimeMillis();
 72             testNewOutputStream(lineNum,contentBytes);
 73             end = System.currentTimeMillis();
 74             long timeElapse_NewOutputStream = end - begin;
 75             bufferedWriter.write(String.valueOf(timeElapse_NewOutputStream)+"\\t");
 76         }
 77         System.out.println("Test NewOutputStream end.\\n");
 78 
 79         System.out.println("Test NewBufferedWriter begin.");
 80         bufferedWriter.write("\\n");
 81         for (int lineNum = startlineNum; lineNum < maxlineNum; lineNum *= Multiplying) {
 82             begin = System.currentTimeMillis();
 83             testNewBufferedWriter(lineNum,contentChars);
 84             end = System.currentTimeMillis();
 85             long timeElapse_NewBufferedWriter = end - begin;
 86             bufferedWriter.write(String.valueOf(timeElapse_NewBufferedWriter)+"\\t");
 87         }
 88         System.out.println("Test NewOutputStream end.\\n");
 89 
 90         bufferedWriter.close();
 91     }
 92 
 93     /************************** I/O *****************************/
 94     //面向字节
 95     public static void testFileOutputStream (int lineNum, byte[] content) throws IOException {
 96         FileOutputStream fileOutputStream = new FileOutputStream(new File("./testFileOutputStream.txt"));
 97         while (--lineNum > 0) {
 98             fileOutputStream.write(content);
 99         }
100         fileOutputStream.close();
101     }
102 
103     public static void testBufferedOutputStream (int lineNum, byte[] content) throws IOException {
104         FileOutputStream fileOutputStream = new FileOutputStream(new File("./testBufferedOutputStream.txt"));
105         BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
106         while (--lineNum > 0) {
107             bufferedOutputStream.write(content);
108         }
109         bufferedOutputStream.close();
110     }
111 
112     //面向字符
113     public static void testFileWriter (int lineNum, String content) throws IOException {
114         FileWriter fileWriter = new FileWriter("./testFileWriter.txt");
115         while (--lineNum > 0) {
116             fileWriter.write(content);
117         }
118         fileWriter.close();
119     }
120 
121     public static void testBufferedWriter (int lineNum, String content) throws IOException {
122         FileWriter fileWriter = new FileWriter("./testBufferedWriter.txt");
123         BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
124         while (--lineNum > 0) {
125             bufferedWriter.write(content);
126         }
127         bufferedWriter.close();
128     }
129 
130 
131     /************************** NIO ****************************/
132     public static void testNewOutputStream (int lineNum, byte[] content) throws IOException {
133         OutputStream outputStream = Files.newOutputStream(Paths.get("./testNewOutputStream.txt"));
134         while (--lineNum > 0) {
135             outputStream.write(content);
136         }
137         outputStream.close();
138     }
139 
140     public static void testNewBufferedWriter (int lineNum,String content) throws IOException {
141         BufferedWriter newBufferedReader = Files.newBufferedWriter(Paths.get("./testNewBufferedWriter.txt"));
142         while (--lineNum > 0) {
143             newBufferedReader.write(content);
144         }
145         newBufferedReader.close();
146     }
147 
148 
149     public static void main (String[] args) throws IOException {
150         //多次测试时可清空result.txt文件
151         FileWriter fileWriter = new FileWriter("./Result.txt");
152         testDriver();
153     }
154 }
View Code

结果为:

可以看出和前面的案例几乎没有差异(图就不画了)。

所以XXXOutputStream效率低的原因并不是字符串转换成字节数组,而是其本身的实现方式所致。

 

~~~~~~~~~~~~~~~~~~~~~分割线:底层实现原理浅谈~~~~~~~~~~~~~~~~~~~~~~~

其实,计算机中都是针对字节操作的(即字符都要经过编码转换为字节),那么问题来了,FileOutputStream为什么比FileWriter(FileWriter内部还有FileOutputStream转换操作,具体看源码)还要慢呢?且慢,让我们把写入文件的数据改一下:

  1 import java.io.File;
  2 import java.io.FileOutputStream;
  3 import java.io.*;
  4 import java.nio.file.Files;
  5 import java.nio.file.Paths;
  6 
  7 public class testFileIO {
  8 
  9 
 10     public static void testDriver () throws IOException {
 11         int maxlineNum = 500001;//写入文件的最大行数
 12         int startlineNum = 1;//写入文件的行数
 13         int Multiplying = 2;//行数增长倍率
 14 
 15         String baseContent = "背景\\n" +
 16             "考虑以下场景:\\n" +
 17             "\\n" +
 18             "InfoTable(信息表):\\n" +
 19             "\\n" +
 20             "Name\\tGender\\tAge\\tScore\\n" +
 21             "张三\\t男\\t21\\t90\\n" +
 22             "李四\\t女\\t20\\t87\\n" +
 23             "王五\\t男\\t22\\t92\\n" +
 24             "赵六\\t女\\t19\\t94\\n" +
 25             "孙七\\t女\\t23\\t88\\n" +
 26             "周八\\t男\\t20\\t91\\n" +
 27             "StatusTable(状态表,指是否有在考试之前复习):\\n" +
 28             "\\n" +
 29             "Name\\thasReview\\n" +
 30             "张三\\t是\\n" +
 31             "李四\\t否\\n" +
 32             "王五\\t是\\n" +
 33             "赵六\\t是\\n" +
 34             "孙七\\t否\\n" +
 35             "周八\\t是\\n" +
 36             "现在,我想知道所有复习过的学生的成绩,可以利用mysql中的子查询来实现:\\n" +
 37             "\\n" +
 38             "SELECT Score \\n" +
 39             "FROM InfoTable \\n" +
 40             "WHERE Name in (SELECT Name \\n" +
 41             "               FROM StatusTable \\n" +
 42             "               WHERE hasReview = \'是\');\\n" +
 43             "这种方式非常方便,我们只要把查询条件写出来,剩下的操作都由mysql来处理。而在实际场景中,为了减少底层耦合,我们一般不通过mysql中的子查询方式联表查询,而是先执行子查询得到结果集,再以结果集作为条件执行外层查询。通常情况下,子查询和外层查询由上层的不同服务执行,这样就在一定程度上达到了底层数据库解耦的目的。注意这种实现方式将mysql内部的一部分复杂操作抛给了我们。这时,Mybatis中的foreach标签就有了用武之地。\\n" +
 44             "\\n" +
 45             "Mybatis 中foreach标签的用法\\n" +
 46             "还以刚才的例子来说,先执行子查询\\n" +
 47             "\\n" +
 48             "SELECT Name FROM StatusTable WHERE hasReview = \'是\'\\n" +
 49             "再执行外层查询,就是\\n" +
 50             "\\n" +
 51             "SELECT Score \\n" +
 52             "FROM InfoTable \\n" +
 53             "WHERE Name in (\'张三\' , \'王五\', \'赵六\', \'周八\');\\n" +
 54             "也就是一个批量查询操作,将其抽象一下(假设有三个条件):\\n" +
 55             "\\n" +
 56             "SELECT * \\n" +
 57             "FROM <tableName> \\n" +
 58             "WHERE <ColumnName> IN (<case1>,<case2>,<case3>)\\n" +
 59             "实际情况中,case可能远不止3个,这时可以在XXXMapper.xml文件中利用Mybatis中的foreach编写sql语句:\\n" +
 60             "\\n" +
 61             "SELECT * \\n" +
 62             "FROM <tableName> \\n" +
 63             "WHERE <ColumnName> IN \\n" +
 64             "<foreach collection=\\"list\\" index=\\"index\\" item=\\"item\\" open=\\"(\\" separator=\\",\\" close=\\")\\">\\n" +
 65             "    #{item}\\n" +
 66             "</foreach>\\n" +
 67             "就可以实现相同的效果了。\\n" +
 68             "\\n" +
 69             "那么问题来了,foreach标签中各种参数是什么含义呢?\\n" +
 70             "\\n" +
 71             "collection\\n" +
 72             "如果传入的是单参数且参数类型是一个List的时候,collection属性值为list\\n" +
 73             "如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array\\n" +
 74             "如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传入参数的时候,在breast里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key\\n" +
 75             "index 

以上是关于Java文件操作类效率对比的主要内容,如果未能解决你的问题,请参考以下文章

java 读CSV 和 Excel

Java List遍历方法及其效率对比

java new 和 clone 的效率比较

solr分布式索引实战分片配置读取:工具类configUtil.java,读取配置代码片段,配置实例

Java字符串连接的多种实现方法及效率对比

elfinder源码浏览-Volume文件系统操作类