荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: icefire (......), 信区: Java
标  题: [转载] 提高Java I/O性能的两个技巧
发信站: BBS 荔园晨风站 (Sun Jan 31 20:03:15 1999), 转信

【 以下文字转载自 Program 讨论区 】
【 原文由 Peter 所发表 】

  由于JDK1.0.2提供的Java.io包设计不周,Java的输入/输出已成为
限制众多Java应用的瓶颈。问题的关键是java.io中的大多数类都没
有缓冲区。实际上,有缓冲区的类只有Buffe redInputStream和Buffe
redOutputStream,但是它们提供的方法非常有限。例如,在许多与文
件相关的应用中,需要逐行对文件进行语法分析,但是只有DataInputS
tream类支持readLine 方法,这个类没有内置的缓冲区,readLine方法
实际上是以字符"\n"或"\r\n"为一行的界定符,一个字符一个字符地
读数据流。每次的字符读取操作使文件的输入/输出变得复杂,尤其是
读一个大文件时I/O效率更低,在没提供缓冲区的情况下,操作一个五
百万字节的文件至少要读取五百万次字符。
    新的JDK1.1增加了一批Reader类和Writer类来提高Java的输入/
输出性能。在处理大文件时,BufferedReader类支持的readLine方法
比DataInputStream类支持的readLine方法至少要快10到20倍。遗憾
的是,JDK1.1并不能解决所有的性能问题。例如,在要对一个大文件进
行语法分析却不想将它读入内存的情况下,RandomAccessFile是一个
很有用的类,但JDK
    1.1仍没有为它设置缓冲区,又没有提供相应的Reader类。处理此
类I/O问题时,JDK1.1就显出它效率低下的缺憾。

    如何处理与缓冲区有关的I/O问题
    处理文件I/O效率低下的问题,我们需要一个有内置缓冲区的Rand
omAccessFile类。为了能继承此类中所有的方法,我们定义一个Rando
mAccessFile类的子类,命名为Braf(Buffered randomaccessfile)。
    public class Braf extends RandomAccessFile{
    }
    为了提高效率,我们定义一个byte型缓冲区而不是char型缓冲区
。变量buf—end、buf—pos和real—pos被用来记录缓冲区中的有效
地址:
    byte buffer[];
    int buf—end=0;
    int buf—pos=0;
    long real—pos=0;
    补充一个用来说明缓冲区大小的参数,重写Braf类的构造方法:
    public Braf(String filename,String mode,int bufsize)thro
ws IOException{
    super(filename,mode);
    invalidate();
    BUF—SIZE=bufsize;
    buffer=new byte[BUF—SIZE];
    }
    重载父类的read方法,使新的read方法总是首先从缓冲区中读取
数据,直到缓冲区中的空间用完,才调用父类中的read方法。因为当缓
冲区空间用尽时,新的read方法会调用fillBuf fer方法来填充缓冲区
。在方法fillBuffer中,调用父类本身的read方法。访问权限private
的invalidate方法被用来指示缓冲区不再容纳有效内容。当seek方
法把文件指针移出缓冲区时,invalidate方法的指示就显得非常必要

    public final int read() throws IOException{
    if(buf—pos >=buf—end) {
        if(fillBuffer() < 0)
       return -1;
    }
    if(buf—end == 0) {
    return-1;
    }else{
        return buffer[buf—pos++];
    }
    }
    private int fillBuffer() throws IOException{
      int n=super.read(buffer,0,BUF—SIZE);
      if(n>=0){
        real—pos +=n;
        buf—end = n;
        buf—pos = 0;
      }
      return n;
    }
    private void invalidate() throws IOException{
      buf—end = 0;
      buf—pos = 0;
      real—pos=super.getFilePointer();
    }
    重载另一个带有参数的read方法。若是有足够的缓冲区,新的rea
d方法将简单调用Syst em.arraycopy,把部分缓冲区直接拷贝到用户
区。由于在代替readLine方法的getNextLine方法中,read方法得到充
分利用,所以使Java的I/O性能极大地提高。新read方法的代码如下:
    public int read(byte b[],int off,int len)throws IOExcept
ion{
        int leftover=buf—end-buf—pos;
        if(len <= leftover) {
       System.arraycopy(buffer,buf—pos,b,off,len);
       buf—pos +=len;
       return len;
    }
    for(int i=0; i<len; i++){
       int c=this.read();
       if(c !=-1)
    b[off+i〗=(byte)c;
      else{
    return i;
      }
     }
     return len;
    }
    为了充分利用缓冲区给我们带来的便利,最初的getFilePointer
方法和seek方法也需要重载,重载后的新方法大部分时间在缓冲区进
行操作,实现更为简单。
      public long getFilePointer() throws IOException{
      long l = real—pos;
      return (l-buf—end+buf—pos);
    }
    public void seek(long pos)throws IOException{
      int n=(int)(real—pos—pos);
      if(n>=0 && n<=buf—end) {
        buf—pos=buf—end-n;
      }else{
        super.seek(pos);
        invalidate();
      }
    }
    需要着重指出的是,因为父类中readLine方法被定义成final,我
们不能对它进行简单的重载,而是用一个新方法getNextLine方法,来
代替readLine方法。getNextLine方法首先判断缓冲区里是否有未读
取的内容,如果没有,就要填充缓冲区。如果在缓冲区中有未读取的内
容并找到新行的界定符,就从缓冲区中读取一个新行,并把它转换成St
ring。否则,就简单地调用read方法一个字节一个字节地读取。虽然
后一部分的代码与父类中的readLine方法相类似,但因为新类中的rea
d方法中用到了缓冲区,就使Java的I/O性能要好得多。
    /*
    **return a next line in string
    */
        public final string getNextLine() throws IOException
{
     String str=null;
     if(buf—end-buf—pos<=0) {
        if(fillBuffer()<0){
       throw new IOException("error in filling buffer!");
    }
      }
      int lineend = -1;
      for(int i=buf—pos;i<buf—end; i++){
      if(buffer[i]==‘\n’){
       lineend=i;
       break;
       }
       }
       if(lineend < 0){
       StringBuffer input=new StringBuffer(256);
       int c;
       while(((c=read())!=-1)&&(c !=‘\n’)){
       input.append((char)c);
       }
       if((c==-1)&&(input.length()==0)) {
        return null;
       }
       return input.toString();
        }
        if(lineend > 0 && buffer[lineend-1]==‘\r’)
       str=new String(buffer,0,buf—pos,lineend-buf—pos -1)
;
        else
      str=new String(buffer,0,buf—pos,lineend-buf—pos);
        buf—pos=lineend+1;
        return str;
    }
    经试验,需要对大型文件进行逐行语法分析时,新类Braf的性能比
RandomAccessFile类至少提高了25倍。
    本文所阐述的方法也适用于那些文件I/O操作较集中的地方。
    关闭Synchronization
    除了上面讨论的输入/输出问题外,降低Java效能的另一个因素是
synchronized语句体。通常,一个synchronized方法的开支大约是通
常方法的6倍。若应用程序不是多线程的,或者在应用程序的某一部分
中只有一个线程执行,就不需要任何并发的部分。当前,Java中还没有
关闭synchronization的方法。一个简单的办法是获取类的源代码,移
去synchronized语句体,并生成一个新类。例如,在类BufferedInputS
tream中,两个read方法是并发的,而所有别的I/O方法是建立在它们的
基础之上的。可以简单地将类名改为NewBIS,例如,从JavaSoft的JD K
1.1提供的BufferedInputStream.java中拷贝这个类的源代码,从NewB
IS.java中移去synch ronized语句体,重新编译NewBIS。

--
※ 来源:.BBS 荔园晨风站 bbs.szu.edu.cn.[FROM: 192.168.1.3]
--
※ 转载:.BBS 荔园晨风站 bbs.szu.edu.cn.[FROM: 192.168.0.26]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店