Java:读取大文件时InputStream太慢

16
我需要逐个字符读取一个 53 MB 的文件。使用 C++ 中的 ifstream 完成只需毫秒级别,但使用 Java InputStream 则需要数分钟。Java 真的会这么慢吗?或者我漏掉了什么?
此外,我必须在 Java 中完成该程序(它使用 servlet,我必须调用处理这些字符的函数)。我考虑过先用 C 或 C++ 编写文件处理部分,然后使用 Java Native Interface 将这些函数与我的 Java 程序接口。这个想法怎么样?
还有其他什么提示吗?我真的需要更快地读取文件。我尝试使用缓冲输入,但仍无法达到 C++ 的性能。
编辑:我的代码跨越多个文件,而且非常混乱,所以我提供了摘要。
import java.io.*;

public class tmp {
    public static void main(String args[]) {
        try{
        InputStream file = new BufferedInputStream(new FileInputStream("1.2.fasta"));
        char ch;        
        while(file.available()!=0) {
            ch = (char)file.read();
                    /* Do processing */
            }
        System.out.println("DONE");
        file.close();
        }catch(Exception e){}
    }
}

1
展示你的代码。如果不看你的实现方式,我们无法猜测你的问题所在。 - Guillaume Polet
1
你是否正在使用BufferedInputStream?相比BufferedReader,你应该使用它。你的访问模式是否可以使用java.nio来内存映射文件的部分内容?具体而言,当你说“逐个字符”时,你是否了解编码以处理那些字节序列可能跨越多个内存映射段的字符? - Mike Samuel
1
仅仅读取那53M个字符而不做任何其他操作,即使有缓冲也不可能花费超过几秒钟的时间。肯定还有其他原因。 - Marko Topolnik
2
逐个读取字符可能就是你的问题所在。 - Louis Wasserman
6
你在错误地使用file.available()。请尝试这样写:while((ch = (char)file.read()) >= 0),同时删除ch = (char)file.read();。这样修改后,你的代码将能够正确读取文件并避免出现错误。 - user845279
显示剩余12条评论
4个回答

17

我用这段代码处理了一个183 MB的文件。它打印出"Elapsed 250 ms"。

final InputStream in = new BufferedInputStream(new FileInputStream("file.txt"));
final long start = System.currentTimeMillis();
int cnt = 0;
final byte[] buf = new byte[1000];
while (in.read(buf) != -1) cnt++;
in.close();
System.out.println("Elapsed " + (System.currentTimeMillis() - start) + " ms");

很好。此外,我需要逐个字符地处理文件。因此,我将从缓冲区中检索它,而不是从文件中读取单个字符,如果缓冲区用尽,则再次填充它。非常感谢 :) - pflz
是的,我认为Java在方法调度上会变得很繁琐,而C++甚至可以内联调用。有时候,在足够多的调用之后,HotSpot也会内联调用,但对于这种情况我不能确定。 - Marko Topolnik
3
这段话的意思是:在这里没有证据表明Java出现了除调用InputStream.available() 5300万次之外的任何问题,这也就是5300万次多余的系统调用。由于他使用了BufferedInputStream,实际读取文件时所需的系统调用次数只有5300/8192万次,因此调用available()将带来巨大的开销。 - user207421
1
@MarkoTopolnik 我使用 BufferedInputStream.read(byte[]) 获得了 676ms 的速度;使用 FileInputStream.read(byte[]) 则需要 2441ms,而使用 BufferedInputStream.read()(每次读取一个字节)则需要 610ms。这些测试文件都是随机内容的,大小均为 183MB,缓冲区大小为 1000 字节。测试环境为 Java 6,多次运行结果相当稳定,没有出现任何 50x 问题的迹象。 - user207421
2
@EJP 好的,我们可能在谈论 OS X 上糟糕的实现。在看到你的结果后,我重新检查了我的结果。它是真实的。 - Marko Topolnik
显示剩余2条评论

3
我会试一下
// create the file so we have something to read.
final String fileName = "1.2.fasta";
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(new byte[54 * 1024 * 1024]);
fos.close();

// read the file in one hit.
long start = System.nanoTime();
FileChannel fc = new FileInputStream(fileName).getChannel();
ByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
while (bb.remaining() > 0)
    bb.getLong();
long time = System.nanoTime() - start;
System.out.printf("Took %.3f seconds to read %.1f MB%n", time / 1e9, fc.size() / 1e6);
fc.close();
((DirectBuffer) bb).cleaner().clean();

打印

Took 0.016 seconds to read 56.6 MB

DirectBuffer符号未找到。因此,我删除了最后一行,但运行它会抛出java.nio.BufferUnderflowException异常。(53.4 MB文件) - pflz
1
我每次读取8个字节以加快速度,但如果长度不是8的倍数,则效果不佳。您可以改用bb.get()。DirectBuffer在sun.nio.ch中,这使它成为一个内部使用API,可以被删除。 - Peter Lawrey

2

2
我也使用了BufferedInputStream。 InputStream fh = new BufferedInputStream(new FileInputStream("file")); - pflz

1
如上所述,请使用BufferedInputStream。您也可以使用NIO包。请注意,对于大多数文件,BufferedInputStream读取的速度与NIO相同。但是,对于极大的文件,NIO可能会更好,因为您可以进行内存映射文件操作。此外,NIO包支持可中断IO,而java.io包不支持。这意味着,如果您想从另一个线程取消操作,必须使用NIO使其可靠。
ByteBuffer buf = ByteBuffer.allocate(BUF_SIZE);
FileChannel fileChannel = fileInputStream.getChannel();
int readCount = 0;
while ( (readCount = fileChannel.read(buf)) > 0) {
  buf.flip();
  while (buf.hasRemaining()) {
    byte b = buf.get();
  }
  buf.clear();
}

1
我认为内存映射文件对于顺序读取没有任何好处。 - Marko Topolnik
@MarkoTopolnik,它们对任何事情的效益不过是20%左右,但我不知道你为什么认为顺序读取是一种特殊情况。它并不是。磁盘仍然会进行预读取,就像在使用流或读取器时一样。 - user207421
@EJP 是的,但是预读已经在更低的层次上完成了(甚至在磁盘电子学中,在磁盘缓存实现中也是如此)。 - Marko Topolnik
@MarkoTopolnik 为什么MM文件对于顺序读取没有好处? - user207421
1
@EJP MM文件主要是为了随机访问而方便,因为它们提供了一个简单的API来访问文件,就像它是RAM中的数组一样。如果你只是从头到尾运行MM文件,你只会给内存管理器带来压力,并且不会从这种范例中获得任何好处。 - Marko Topolnik
@EJP,我洗耳恭听您的杀手级论据。希望有代码可以玩! - Marko Topolnik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接