java - read用法 - bufferedreader上限




为什么BufferedReader read()比readLine()慢得多? (4)

我需要一次读取一个文件,我正在使用BufferedReaderread()方法。 *

我发现read()readLine()慢大约10倍。 这是预期的吗? 或者我做错了什么?

这是Java 7的基准测试。输入测试文件大约有500万行和2.54亿个字符(~242 MB)**:

read()方法大约需要7000毫秒来读取所有字符:

@Test
public void testRead() throws IOException, UnindexableFastaFileException{

    BufferedReader fa= new BufferedReader(new FileReader(new File("chr1.fa")));

    long t0= System.currentTimeMillis();
    int c;
    while( (c = fa.read()) != -1 ){
        //
    }
    long t1= System.currentTimeMillis();
    System.err.println(t1-t0); // ~ 7000 ms

}

readLine()方法只需约700毫秒:

@Test
public void testReadLine() throws IOException{

    BufferedReader fa= new BufferedReader(new FileReader(new File("chr1.fa")));

    String line;
    long t0= System.currentTimeMillis();
    while( (line = fa.readLine()) != null ){
        //
    }
    long t1= System.currentTimeMillis();
    System.err.println(t1-t0); // ~ 700 ms
}

* 实用目的 :我需要知道每一行的长度,包括换行符( \n\r\n )和剥离后的行长度。 我还需要知道一行是否以>字符开头。 对于给定的文件,这只在程序开始时完成一次。 由于BufferedReader.readLine()不返回EOL字符,因此我采用了read()方法。 如果有更好的方法,请说。

** gzip压缩文件位于http://hgdownload.cse.ucsc.edu/goldenpath/hg19/chromosomes/chr1.fa.gz 。 对于那些可能想知道的人,我正在写一个类来索引fasta文件。


Java JIT优化了空循环体,因此您的循环实际上如下所示:

while((c = fa.read()) != -1);

while((line = fa.readLine()) != null);

我建议你在here阅读基准测试和循环优化。

至于为什么花费的时间不同:

  • 原因一(这仅适用于循环体包含代码的情况):在第一个示例中,您每行执行一次操作,在第二个示例中,每个字符执行一次操作。 这样就可以增加你拥有的线条/字符。

    while((c = fa.read()) != -1){
        //One operation per character.
    }
    
    while((line = fa.readLine()) != null){
        //One operation per line.
    }
  • 原因二:在类BufferedReader ,方法readLine()在幕后不使用read() - 它使用自己的代码。 readLine()方法读取一行的每个字符的操作少于使用read()方法读取一行的操作 - 这就是readLine()读取整个文件的速度更快的原因。

  • 原因三:读取每个字符所需的迭代次数比读取每一个字符要多(除非每个字符都在一个新行上); read()被调用的次数比readLine()


根据文件:

每个read()方法调用都会进行昂贵的系统调用。

但是,每次readLine()方法调用仍会进行昂贵的系统调用,一次查找更多字节,因此调用次数较少。

当我们为要update的每条记录创建数据库update命令时,会发生类似的情况,而不是批量更新,我们会对所有记录进行一次调用。


如果你考虑一下,看到这种差异就不足为奇了。 一个测试是迭代文本文件中的行,而另一个是迭代字符。

除非每行包含一个字符,否则readLine()应该比read()方法更快。(尽管如上面的注释所指出的那样,因为BufferedReader缓冲了输入,所以它是有争议的,而物理文件读取可能不是唯一的表演手术)

如果你真的想测试2之间的差异我会建议你在两个测试中迭代每个字符的设置。 例如:

void readTest(BufferedReader r)
{
    int c;
    StringBuilder b = new StringBuilder();
    while((c = r.read()) != -1)
        b.append((char)c);
}

void readLineTest(BufferedReader r)
{
    String line;
    StringBuilder b = new StringBuilder();
    while((line = b.readLine())!= null)
        for(int i = 0; i< line.length; i++)
            b.append(line.charAt(i));
}

除上述内容外,请使用“Java性能诊断工具”对您的代码进行基准测试。 另外,阅读如何微代码标记java代码


所以这是我自己的问题的实际答案:不要使用BufferedReader.read()而是使用FileChannel 。 (显然我没有回答我为什么选择标题)。 这是快速而肮脏的基准测试,希望其他人会发现它很有用:

@Test
public void testFileChannel() throws IOException{

    FileChannel fileChannel = FileChannel.open(Paths.get("chr1.fa"));
    long n= 0;
    int noOfBytesRead = 0;

    long t0= System.nanoTime();

    while(noOfBytesRead != -1){
        ByteBuffer buffer = ByteBuffer.allocate(10000);
        noOfBytesRead = fileChannel.read(buffer);
        buffer.flip();
        while ( buffer.hasRemaining() ) {
            char x= (char)buffer.get();
            n++;
        }
    }
    long t1= System.nanoTime();
    System.err.println((float)(t1-t0) / 1e6); // ~ 250 ms
    System.err.println("nchars: " + n); // 254235640 chars read
}

使用~250 ms来读取char的整个文件char,这个策略比BufferedReader.readLine() (~700 ms)快得多,更不用说read() 。 在循环中添加if语句以检查x == '\n'x == '>'几乎没有区别。 同时使用StringBuilder重建行也不会对时序造成太大影响。 所以这对我有好处(至少目前为止)。

感谢@ Marco13提及FileChannel。







benchmarking