机器学习和生物信息学实验室联盟

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 16978|回复: 19
打印 上一主题 下一主题

关于Java读写文件速度的测试

  [复制链接]
跳转到指定楼层
楼主
发表于 2012-3-26 18:17:37 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在测试虚拟硬盘的时候,查了一下基本的读取文件的方法,现在将各个方法列举如下,以及测试速度的时间,供大家作为参考,以后如果用到了,可选用合适的读取方式。

1. BufferedReader,BufferedWriter

这一种方法是实验室常用的一种方式,这种方式的好处是可以读取一行,然后针对每一行进行处理,他是构造一个缓冲流,然后进行读写,但是这种方法是相对比较慢的。
参考代码如下:
  1. BufferedReader br = new BufferedReader(new FileReader(args[0]));
  2.                 BufferedWriter bw=new BufferedWriter(new FileWriter(args[1]));
  3.                 String line;
  4.                 while((line=br.readLine())!=null)
  5.                 {
  6.                         bw.write(line);
  7.                 }
  8.                 bw.flush();
  9.                 bw.close();
  10.                 br.close();
复制代码
参数可以自己选用。

2. RandomAccessFile

这一种方法不常用,是随机读取的方式,这种方式比较特殊,他不隶属于InputStream,OutputStream类系,他是直接继承自Object类的。RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream粘起来,再加上它自己的一些方法,比如定位用的getFilePointer( ),在文件里移动用的seek( ),以及判断文件大小的length( )。此外,它的构造函数还要一个表示以只读方式("r"),还是以读写方式("rw")打开文件的参数 (和C的fopen( )一模一样)。它不支持只写文件,从这一点上看,假如RandomAccessFile继承了DataInputStream,它也许会干得更好。

参考代码如下:
  1. RandomAccessFile read = new RandomAccessFile(args[0],"r");   
  2.         RandomAccessFile writer = new RandomAccessFile(args[1],"rw");   
  3.         byte[] b = new byte[1024*1024];
  4.         int count;
  5.         while((count=read.read(b))!=-1){  
  6.                 //System.out.println(count);
  7.                 if(count!=b.length)
  8.                 {
  9.                         byte[] t=new byte[count];
  10.                         for(int i=0;i<count;++i)
  11.                                 t[i]=b[i];
  12.                         writer.write(t);
  13.                 }
  14.                 else writer.write(b);   
  15.         }   
  16.         writer.close();   
  17.         read.close();
复制代码
参数选择可以根据后边标记读写的进行选择,比如说第二个参数是"r",说明这是一个读文件的,如果是"rw",说明是写文件的。

3. FileInputStream,FileOutputSteam

这是一个最基本的读写文件的方式,这种读写一般是读取一个字节,或者定义长度的字节个数,如果为了节约时间的话,建议用缓冲区读写,这样速度会快一点。

参考代码如下:
  1. InputStream in=new FileInputStream(new File(args[0]));
  2.                 OutputStream out=new FileOutputStream(new File(args[1]));
  3.                 int count;
  4.                 byte[] b=new byte[1024*1024];
  5.                 while((count=in.read(b))!=-1)
  6.                 {
  7.                         if(count!=b.length)
  8.                 {
  9.                         byte[] t=new byte[count];
  10.                         for(int i=0;i<count;++i)
  11.                                 t[i]=b[i];
  12.                         out.write(t);
  13.                 }
  14.                         else out.write(b);
  15.                 }
  16.                 in.close();
  17.                 out.flush();
  18.                 out.close();
复制代码
参数和第一个选择一样。

4. 内存直接映射

这是一个内存直接映射读取的方式,如果文件大小合适,其他读取方式相对于他来说就是浮云啊,等一下我把各个大小的比对时间放上,大家应该可以看出来。但是这有一个限制,就是如果文件太大的话,映射内存会出现问题,要么就是内存不够,或者就是读取很慢,我觉得应该是文件太大,映射内存会出现不够的现象,然后内存会启动换入换出之类的,会浪费时间。但是基本的文件读写,他还是秒杀的。

参考代码如下:
  1. FileChannel read = new RandomAccessFile(args[0],"r").getChannel();   
  2.         FileChannel writer = new RandomAccessFile(args[1],"rw").getChannel();   
  3.         long i = 0;   
  4.         long size = read.size()/30;
  5.         //System.out.println(read.size());
  6.         ByteBuffer bb,cc = null;   
  7.         while(i<read.size()&&(read.size()-i)>size)
  8.         {   
  9.             bb = read.map(FileChannel.MapMode.READ_ONLY, i, size);   
  10.             cc = writer.map(FileChannel.MapMode.READ_WRITE, i, size);   
  11.             cc.put(bb);   
  12.             i+=size;   
  13.             bb.clear();   
  14.             cc.clear();   
  15.         }   
  16.         bb = read.map(FileChannel.MapMode.READ_ONLY, i, read.size()-i);
  17.         cc = writer.map(FileChannel.MapMode.READ_WRITE, i, read.size()-i);
  18.         cc.put(bb);   
  19.         bb.clear();   
  20.         cc.clear();   
  21.         read.close();   
  22.         writer.close();
复制代码
参数同上。

下边我列出以下各个大小文件,各种读取方式所花费的时间,仅作参考。
表格里边我会用Buffer作为BufferedReader的简写,Random作为RandomAccessFile的简写,FileInput作为FileInputStream的简写,Map作为内存直接映射的简写。我给出两次测试结果。

146M文件 第一次测试 第二次测试
Buffer 4.737s 4.581s
Random 0.251s 0.249s
FileInput 0.27s 0.269s
Map 0.102s 0.104s

除了BufferedReader之外,其他的读取时间基本上没有太大区别。

356M文件 第一次测试 第二次测试
Buffer 10.889s 10.71s
Random 0.558s 0.589s
FileInput 0.637s 0.635s
Map 0.125s 0.124s


570M文件 第一次测试 第二次测试
Buffer 17.215s 17.393s
Random 2.756s 2.243s
FileInput 1.924s 1.975s
Map 0.203s 0.197s


712M文件 第一次测试 第二次测试
Buffer 21.852s 23.262s
Random 3.481s 3.529s
FileInput 3.829s 3.42s
Map 0.246s 0.258s


998M文件 第一次测试 第二次测试
Buffer 34.08s 32.437s
Random 21.817s 20.589s
FileInput 9.669s 9.792s
Map 15.481s 16.886s


由以上图表可以看出,在文件大小一般的情况下,随着文件大小的增加,内存映射基本保持在1秒以下,基本0.2s左右,而其他的都会随着文件的增大有明显的变化,这就是内存直接映射秒杀的地方,但是就是文件增加到了998M之后,时间突然之间会增加很大,不知道是不是因为文件太大,以致于内存里边需要执行操作系统提供的一些保护算法而浪费时间了,这个有待考证,也可能是每次运行完之后没有清理内存,导致数据堵塞,因为内存映射方式,java没有提供相应的直接回收MappedByteBuffer区域的方法,这也会导致另外一种异常就是内存不够。

然后我在网上搜了两种解决方式,但是我自己也没有实验出来,代码是执行了,但是不知道从哪里看出他执行前后的区别。这里也把代码放上。

第一种方式:
  1. System.gc();   
  2.          System.runFinalization();   
  3.          try {   
  4.     Thread.sleep(3000);   
  5. } catch (InterruptedException e) {   
  6.       
  7.     e.printStackTrace();   
  8. }  
复制代码
第二种方式:
  1. public static void unmap(final MappedByteBuffer buffer) {   
  2.         if (buffer == null) {   
  3.             return;   
  4.         }   
  5.         AccessController.doPrivileged(new PrivilegedAction<Object>() {   
  6.             public Object run() {   
  7.                 try {   
  8.                     Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]);   
  9.                     if (getCleanerMethod != null) {   
  10.                         getCleanerMethod.setAccessible(true);   
  11.                         Object cleaner = getCleanerMethod.invoke(buffer, new Object[0]);   
  12.                         Method cleanMethod = cleaner.getClass().getMethod("clean", new Class[0]);   
  13.                         if (cleanMethod != null) {   
  14.                             cleanMethod.invoke(cleaner, new Object[0]);   
  15.                         }   
  16.                     }   
  17.                 } catch (Exception e) {   
  18.                     e.printStackTrace();   
  19.                 }   
  20.                 return null;   
  21.             }   
  22.    
  23.         });   
  24.     }  
复制代码
但是执行这个之后,会把时间拖慢,也是一个问题,所以这个问题待解决。


上边是我测试了几个方式的读取方式,这样以后实验室的使用读取文件的方式的时候,可以选用最适合的方式,如果时间是最紧要的,那么可以选用内存直接映射,但是这个要保证内存够用,而且文件合适的情况下,至于上边的那个问题,下边我会继续关注,如果谁了解了,欢迎提示~

如果谁熟悉其他的方式的,欢迎跟帖~
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 转播转播 分享分享
回复

使用道具 举报

沙发
发表于 2012-3-26 18:47:27 | 只看该作者
本帖最后由 xmubingo 于 2012-3-26 18:53 编辑

,很全面!!

BufferReader是一次性读取一个Buffer size大小到缓冲区。
BufferedReader(Reader r, int size);可以设置缓冲区大小。

你的测试结果是:
对于BufferedReader按一行行读取然后写入。
对于RandomAccessFile是按1024*1024bytes读取然后写入。
由于他们每次读取的大小不一致,因为他们花费的时间理论上本来就不一样。希望能公平测试。

RandomAccessFile也有readLine这个方法,你可以和BufferedReader的readLine方法进行对比,同时对一个大文件进行读一行写一行的测试。看看各自花多少时间。我相信这样的话Buffer的作用就显现出来了。

FileInputStream可以控制文件流的编码格式。这个之前chenwq讨论过。
回复 支持 反对

使用道具 举报

板凳
发表于 2012-3-26 19:06:07 | 只看该作者
very gooooooooooooooooood
回复 支持 反对

使用道具 举报

地板
 楼主| 发表于 2012-3-26 22:01:31 | 只看该作者
xmubingo 发表于 2012-3-26 18:47
,很全面!!

BufferReader是一次性读取一个Buffer size大小到缓冲区。

其实我想突出的是那个内存直接映射方式,简直秒杀其他读取方式~
回复 支持 反对

使用道具 举报

5#
发表于 2012-3-26 22:47:19 | 只看该作者
hsc 发表于 2012-3-26 22:01
其实我想突出的是那个内存直接映射方式,简直秒杀其他读取方式~

嗯,速度很快。但是最好能支持按行读取,而且对文件大小有限制。
回复 支持 反对

使用道具 举报

6#
 楼主| 发表于 2012-3-26 22:48:28 | 只看该作者
xmubingo 发表于 2012-3-26 18:47
,很全面!!

BufferReader是一次性读取一个Buffer size大小到缓冲区。

针对你说的BufferedReader的也可以设置缓冲区长度的读取,我和ReadAccessFile的缓冲区读取进行比较,同样设置1024*1024的,对于380M的文件,RandomAccessFile需要0.6s左右,而BufferedReader需要6s左右,错了大概10倍。

BufferedReader的缓冲区读取代码如下:
  1. BufferedReader br = new BufferedReader(new FileReader(args[0]));
  2.         BufferedWriter bw=new BufferedWriter(new FileWriter(args[1]));
  3.         String line;
  4.         int count;
  5.         char[] buffer=new char[1024*1024];
  6.         while((count=br.read(buffer))!=-1)
  7.         {
  8.                 //System.out.println(count);
  9.                 if(count!=buffer.length)
  10.                 {
  11.                         char[] t=new char[count];
  12.                         for(int i=0;i<count;++i)
  13.                         {
  14.                                 t[i]=buffer[i];
  15.                         }
  16.                         bw.write(t);
  17.                 }
  18.                 else bw.write(buffer);
  19.         }
复制代码
可以看出使用缓冲区会提高速度,但是相对来说还是RandomAccessFile会高一点

380M 第一次 第二次
Buffer 6.14s 6.075
Random 0.571s 0.581s

回复 支持 反对

使用道具 举报

7#
发表于 2012-3-26 23:20:05 | 只看该作者
hsc 发表于 2012-3-26 22:48
针对你说的BufferedReader的也可以设置缓冲区长度的读取,我和ReadAccessFile的缓冲区读取进行比较,同样 ...

嗯!你的实验很有价值!!!对于读较大的内容,ReadAccessFile会快。

我很想知道,如果用两者,每个方法都用readLine读一行,再write一行,那么380M,看看谁花的时间多点?
回复 支持 反对

使用道具 举报

8#
 楼主| 发表于 2012-3-27 11:49:57 | 只看该作者
xmubingo 发表于 2012-3-26 23:20
嗯!你的实验很有价值!!!对于读较大的内容,ReadAccessFile会快。

我很想知道,如果用两者,每个方 ...

这个还在找方法,因为ReadAccessFile的readline()是读string,但是没有直接写这个,用其他的方式写总是乱码~
回复 支持 反对

使用道具 举报

9#
发表于 2012-3-27 18:11:34 | 只看该作者
hsc 发表于 2012-3-27 11:49
这个还在找方法,因为ReadAccessFile的readline()是读string,但是没有直接写这个,用其他的方式写总是乱 ...

这样啊,是不是编码的问题呢。
http://hi.baidu.com/cpf_51940889 ... 1e3daad1a2d3e6.html
回复 支持 反对

使用道具 举报

10#
 楼主| 发表于 2012-3-27 20:32:38 | 只看该作者
xmubingo 发表于 2012-3-27 18:11
这样啊,是不是编码的问题呢。
http://hi.baidu.com/cpf_519408891/blog/item/8ffba63de91e3daad1a2d3e6 ...

确实是编码的问题,但是使用重新编码之后,ReadAccessFile读取一行写入一行,380M的文件需要400多秒,而BufferedReader需要六七秒,两者错了差不多十倍,不知道是不是编码的问题呢,还是ReadAccessFile读取行就是比较慢。

参考代码如下:
  1. RandomAccessFile read = new RandomAccessFile(args[0],"r");   
  2.         RandomAccessFile writer = new RandomAccessFile(args[1],"rw");   
  3.         byte[] b = new byte[1024*1024];
  4.         int count;
  5.         String line;
  6.         while((line=read.readLine())!=null)
  7.         {
  8.                 line+="\r\n";
  9.                 writer.writeBytes(new String(line.getBytes(),"gb2312"));
  10.                 //writer.writeBytes(new String("\r\n","gb2312"));
  11.         }
复制代码
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

机器学习和生物信息学实验室联盟  

GMT+8, 2024-11-27 05:58 , Processed in 0.128477 second(s), 18 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表