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 }
结果为:
可以看出和前面的案例几乎没有差异(图就不画了)。
所以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文件操作类效率对比的主要内容,如果未能解决你的问题,请参考以下文章