IO性能分析.docx

上传人:b****5 文档编号:3274244 上传时间:2022-11-21 格式:DOCX 页数:24 大小:24.04KB
下载 相关 举报
IO性能分析.docx_第1页
第1页 / 共24页
IO性能分析.docx_第2页
第2页 / 共24页
IO性能分析.docx_第3页
第3页 / 共24页
IO性能分析.docx_第4页
第4页 / 共24页
IO性能分析.docx_第5页
第5页 / 共24页
点击查看更多>>
下载资源
资源描述

IO性能分析.docx

《IO性能分析.docx》由会员分享,可在线阅读,更多相关《IO性能分析.docx(24页珍藏版)》请在冰豆网上搜索。

IO性能分析.docx

IO性能分析

IO性能分析

本文大多技术围绕调整磁盘文件I/O,但是有些内容也同样适合网络I/O和窗口输出。

第一部分技术讨论底层的I/O问题,然后讨论诸如压缩,格式化和串行化等高级I/O问题。

然而这个讨论没有包含应用设计问题,例如搜索算法和数据结构,也没有讨论系统级的问题,例如文件高速缓冲。

Java语言采取两种截然不同的磁盘文件结构。

一个是基于字节流,另一个是字符序列。

在Java语言中一个字符有两个字节表示,而不是像通常的语言如c语言那样是一个字节。

因此,从一个文件读取字符时需要进行转换。

这个不同在某些情况下是很重要的,就像下面的几个例子将要展示的那样。

低级I/O相关的问题:

∙缓冲

∙读写文本文件

∙格式化的代价

∙随机访问

高级I/O问题

∙压缩

∙高速缓冲

∙分解

∙串行化

∙获取文件信息

∙更多信息

加速I/O的基本规则

∙避免访问磁盘

∙避免访问底层的操作系统

∙避免方法调用

∙避免个别的处理字节和字符

很明显这些规则不能在所有的问题上避免,因为如果能够的话就没有实际的I/O被执行。

使用缓存减少读写次数开销

使用缓冲加速文件读取的示例:

对于一个1MB的输入文件,以秒为单位的执行时间是:

 FileInputStream的read方法,每次读取一个字节,不用缓冲            6.9秒

 BufferedInputStream的read方法使用BufferedInputStream             0.9秒

 FileInputStream的read方法读取数据到直接缓冲                      0.4秒

或者说在最慢的方法和最快的方法间是17比1的不同。

这个巨大的加速并不能证明你应该总是使用第三种方法,即自己做缓冲。

这可能是一个错误的倾向特别是在处理文件结束事件时没有仔细的实现。

在可读性上它也没有其它方法好。

但是记住时间花费在哪儿了以及在必要的时候如何矫正是很有用。

方法2或许是对于大多应用的"正确"方法.

方法2和3使用了缓冲技术,大块文件被从磁盘读取,然后每次访问一个字节或字符。

缓冲是一个基本而重要的加速I/O的技术,而且有几个类支持缓冲(BufferedInputStream用于字节,BufferedReader用于字符)。

缓冲区越大I/O越快吗?

典型的Java缓冲区长1024或者2048字节,一个更大的缓冲区有可能加速I/O但比重很小,大约5到10%。

方法1:

读方法

第一个方法简单的使用FileInputStream的read方法:

FileInputStream的read方法每次读取文件的下一个字节,触发了大量的底层运行时系统调用

优点:

编码简单,适用于小文件

缺点:

读写频繁,不适用于大文件

 importjava.io.*;

 publicclassintro1{

   publicstaticvoidmain(Stringargs[]){

     if(args.length!

=1){

       System.err.println("missingfilename");

       System.exit

(1);

     }

     try{

       FileInputStreamfis=newFileInputStream(args[0]);//建立指向文件的读写流

       intcnt=0;

       intb;

       while((b=fis.read())!

=-1){ //FileInputStream的read方法每次读取文件一个字节

         if(b=='\n')

           cnt++;

       }

       fis.close();

       System.out.println(cnt);

     }

     catch(IOExceptione){

       System.err.println(e);

     }

   }

 }

方法2:

使用大缓冲区

第二种方法使用大缓冲区避免了上面的问题:

BufferedInputStream的read方法把文件的字节块读入缓冲区,然后每次读取一个字节,每次填充缓冲只需要访问一次底层存储接口

优点:

避免每个字节的底层读取,编码相对不复杂

缺点:

缓存占用了小量内存

importjava.io.*;

 publicclassintro2{

  publicstaticvoidmain(Stringargs[]){

   if(args.length!

=1){

     System.err.println("missingfilename");

     System.exit

(1);

   }

   try{

     FileInputStreamfis= newFileInputStream(args[0]);

     BufferedInputStreambis=newBufferedInputStream(fis);//把文件读取流指向缓冲区

     intcnt=0;

     intb;

     while((b=bis.read())!

=-1){  //BufferedInputStream的read方法把文件的字节块独     

                                 //入缓冲区BufferedInputStream,然后每次读取一个字节

       if(b=='\n')

         cnt++;

       }

     bis.close();

     System.out.println(cnt);

   }

   catch(IOExceptione){

     System.err.println(e);

   }

 }

 }

方法3:

直接缓冲

FileInputStream的read方法直接读入字节块到直接缓冲buf,然后每次读取一个字节。

优点:

速度最快,

缺点:

编码稍微复杂,可读性差,占用了小量内存,

importjava.io.*;

 publicclassintro3{

   publicstaticvoidmain(Stringargs[]){

     if(args.length!

=1){

       System.err.println("missingfilename");

       System.exit

(1);

     }

     try{

       FileInputStreamfis=newFileInputStream(args[0]);

       bytebuf[]=newbyte[2048];

       intcnt=0;

       intn;

       while((n=fis.read(buf))!

=-1){//FileInputStream的read方法直接读入字节块到

                                    //直接缓冲buf,然后每次读取一个字节

         for(inti=0;i

           if(buf[i]=='\n')

             cnt++;

         }

       }

       fis.close();

       System.out.println(cnt);

     }

     catch(IOExceptione){

       System.err.println(e);

     }

   }

 }

方法4:

缓冲整个文件

缓冲的极端情况是事先决定整个文件的长度,然后读取整个文件。

优点:

把文件底层读取降到最少,一次,

缺点:

大文件会耗尽内存。

importjava.io.*;

 publicclassreadfile{

   publicstaticvoidmain(Stringargs[]){

     if(args.length!

=1){

       System.err.println("missingfilename");

       System.exit

(1);

     }

     try{

       intlen=(int)(newFile(args[0]).length());

       FileInputStreamfis=newFileInputStream(args[0]);

       bytebuf[]=newbyte[len];                 //建立直接缓冲

       fis.read(buf);                            //读取整个文件

       fis.close();

       intcnt=0;

       for(inti=0;i

         if(buf[i]=='\n')

           cnt++;

       }

       System.out.println(cnt);

     }

     catch(IOExceptione){

       System.err.println(e);

     }

   }

 }

这个方法很方便,在这里文件被当作一个字节数组。

但是有一个明显得问题是有可能没有读取一个巨大的文件的足够的内存。

缓冲的另一个方面是向窗口终端的文本输出。

缺省情况下,System.out(一个PrintStream)是行缓冲的,这意味着在遇到一个新行符后输出缓冲区被提交。

格式化的代价

实际上向文件写数据只是输出代价的一部分。

另一个可观的代价是数据格式化。

考虑下面的字符输出程序

性能对比结果为:

这些程序产生同样的输出。

运行时间是:

格式化方法

示例语句

运行时间

简单的输出一个固定字符

System.out.print(s);

1.3秒

使用简单格式"+"

Strings=字符+字符,       

System.out.print(s);

 

1.8秒

使用java.text包中的MessageFormat类的对象方法

Strings=fmt.format(values);

System.out.print(s);

7.8秒

使用java.text包中的MessageFormat类的静态方法

 

7.8*1.3秒

最慢的和最快的大约是6比1。

如果格式没有预编译第三种方法将更慢,使用静态的方法代替:

第三个方法比前两种方法慢很多的事实并不意味着你不应该使用它,而是你要意识到时间上的开销。

在国际化的情况下信息格式化是很重要的,关心这个问题的应用程序通常从一个绑定的资源中读取格式然后使用它。

方法1,简单的输出一个固定的字符串,了解固有的I/O开销:

publicclassformat1{

   publicstaticvoidmain(Stringargs[]){

     finalintCOUNT=25000;

     for(inti=1;i<=COUNT;i++){

       Strings="Thesquareof5is25\n";

       System.out.print(s);

     }

   }

 }

方法2,使用简单格式"+":

publicclassformat2{

   publicstaticvoidmain(Stringargs[]){

     intn=5;

     finalintCOUNT=25000;

     for(inti=1;i<=COUNT;i++){

       Strings="Thesquareof"+n+"is"+

           n*n+"\n";

       System.out.print(s);

     }

   }

 }

方法3,第三种方法使用java.text包中的MessageFormat类的对象方法:

importjava.text.*;

 publicclassformat3{

  publicstaticvoidmain(Stringargs[]){

    MessageFormatfmt=

     newMessageFormat("Thesquareof{0}is{1}\n");

     Objectvalues[]=newObject[2];

   intn=5;

   values[0]=newInteger(n);

   values[1]=newInteger(n*n);

   finalintCOUNT=25000;

   for(inti=1;i<=COUNT;i++){

     Strings=fmt.format(values);

     System.out.print(s);

    }

   }

 }

方法4,使用MessageFormat.format(String,Object[])类的静态方法

importjava.text.*;

 publicclassformat4{

   publicstaticvoidmain(Stringargs[]){

     Stringfmt="Thesquareof{0}is{1}\n";

     Objectvalues[]=newObject[2];

     intn=5;

     values[0]=newInteger(n);

     values[1]=newInteger(n*n);

     finalintCOUNT=25000;

     for(inti=1;i<=COUNT;i++){

       Strings=

           MessageFormat.format(fmt,values);

       System.out.print(s);

     }

   }

 }

这比前一个例子多花费1/3的时间。

随机访问的性能开销

RandomAccessFile是一个进行随机文件I/O(在字节层次上)的类。

这个类提供一个seek方法,和C/C++中的相似,移动文件指针到任意的位置,然后从那个位置字节可以被读取或写入。

seek方法访问底层的运行时系统因此往往是消耗巨大的。

一个更好的代替是在RandomAccessFile上建立你自己的缓冲,并实现一个直接的字节read方法。

read方法的参数是字节偏移量(>=0)。

这样的一个例子是:

这个程序简单的读取字节序列然后输出它们。

适用的情况:

如果有访问位置,这个技术是很有用的,文件中的附近字节几乎在同时被读取。

例如,如果你在一个排序的文件上实现二分法查找,这个方法可能很有用。

不适用的情况:

如果你在一个巨大的文件上的任意点做随机访问的话就没有太大价值。

importjava.io.*;

 publicclassReadRandom{

   privatestaticfinalintDEFAULT_BUFSIZE=4096;

   privateRandomAccessFileraf;

   privatebyteinbuf[];

   privatelongstartpos=-1;

   privatelongendpos=-1;

   privateintbufsize;

   publicReadRandom(Stringname)

    throwsFileNotFoundException{

     this(name,DEFAULT_BUFSIZE);

   }

   publicReadRandom(Stringname,intb)

       throwsFileNotFoundException{

     raf=newRandomAccessFile(name,"r");

     bufsize=b;

     inbuf=newbyte[bufsize];

   }

   publicintread(longpos){

     if(posendpos){

       longblockstart=(pos/bufsize)*bufsize;

       intn;

       try{

         raf.seek(blockstart);

         n=raf.read(inbuf);

       }

       catch(IOExceptione){

         return-1;

       }

       startpos=blockstart;

       endpos=blockstart+n-1;

       if(posendpos)

         return-1;

     }

     returninbuf[(int)(pos-startpos)]&0xffff;

   }

   publicvoidclose()throwsIOException{

     raf.close();

   }

   publicstaticvoidmain(Stringargs[]){

     if(args.length!

=1){

       System.err.println("missingfilename");

       System.exit

(1);

     }

     try{

       ReadRandomrr=newReadRandom(args[0]);

       longpos=0;

       intc;

       bytebuf[]=newbyte[1];

       while((c=rr.read(pos))!

=-1){

         pos++;

         buf[0]=(byte)c;

         System.out.write(buf,0,1);

       }

       rr.close();

     }

     catch(IOExceptione){

       System.err.println(e);

     }

   }

 }

压缩的性能开销

Java提供用于压缩和解压字节流的类,这些类包含在java.util.zip包里面,这些类也作为Jar文件的服务基础(Jar文件是带有附加文件列表的Zip文件)。

压缩的目的是减少存储空间,同时被压缩的文件在IO速度不变的情况下会减少传输时间。

压缩时候要消耗CPU时间,占用内存。

压缩时间=数据量/压缩速度。

IO传输时间=数据容量/IO速度。

传输数据的总时间=压缩时间+I/O传输时间

压缩是提高还是损害I/O性能很大程度依赖你的硬件配置,特别是和处理器和磁盘驱动器的速度相关。

使用Zip技术的压缩通常意味着在数据大小上减少50%,但是代价是压缩和解压的时间。

一个巨大(5到10MB)的压缩文本文件,使用带有IDE硬盘驱动器的300-MHzPentiumPC从硬盘上读取可以比不压缩少用大约1/3的时间。

压缩的一个有用的范例是向非常慢的媒介例如软盘写数据。

使用高速处理器(300MHzPentium)和低速软驱(PC上的普通软驱)的一个测试显示压缩一个巨大的文本文件然后在写入软盘比直接写入软盘快大约50%。

下面的程序接收一个输入文件并将之写入一个只有一项的压缩的Zip文件:

importjava.io.*;

 importjava.util.zip.*;

 publicclasscompress{

   publicstaticvoiddoit(Stringfilein,Stringfileout){

     FileInputStreamfis=null;

     FileOutputStreamfos=null;

     try{

       fis=newFileInputStream(filein);

       fos=newFileOutputStream(fileout);

       ZipOutputStreamzos= newZipOutputStream(fos);

       ZipEntryze=newZipEntry(filein);

       zos.putNextEntry(ze);

       finalintBUFSIZ=4096;

       byteinbuf[]=newbyte[BUFSIZ];

       intn;

       while((n=fis.read(inbuf))!

=-1)

         zos.write(inbuf,0,n);

       fis.close();

       fis=null;

       zos

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 英语

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1