Java -- 从文件中读取数据。输入流 vs. 读取器

14

我看到的每个Java实现中,读取文件几乎总是使用FileReader逐行读取。 我认为这会非常低效,因为它需要每行执行一次系统调用。

相反,我使用输入流直接获取字节。 在我的实验中,这样做明显更快。 我的测试是一个1MB的文件。

    //Stream method
    try {
        Long startTime = new Date().getTime();

        InputStream is = new FileInputStream("test");
        byte[] b = new byte[is.available()];
        is.read(b);
        String text = new String(b);
        //System.out.println(text);

        Long endTime = new Date().getTime();
        System.out.println("Text length: " + text.length() + ", Total time: " + (endTime - startTime));

    }
    catch (Exception e) {
        e.printStackTrace();
    }

    //Reader method
    try {
        Long startTime = new Date().getTime();

        BufferedReader br = new BufferedReader(new FileReader("test"));
        String line = null;
        StringBuilder sb = new StringBuilder();
        while ((line = br.readLine()) != null) {
            sb.append(line);
            sb.append("\n");
        }
        String text = sb.toString();

        Long endTime = new Date().getTime();
        System.out.println("Text length: " + text.length() + ", Total time: " + (endTime - startTime));

    }
    catch (Exception e) {
        e.printStackTrace();
    }

这将得到一个结果:

Text length: 1054631, Total time: 9
Text length: 1034099, Total time: 22

那么,为什么人们使用读取器而不是流?

如果我有一个接受文本文件并返回包含所有文本的字符串的方法,那么使用流一定更好吗?


你的代码不正确。不能保证它会读取整个文件,请查看read和available方法的文档。 - Milo
1
你是否尝试过使用java.nio.File包中的Files.readAllLines(...)方法? - nIcE cOw
+1 for learned something new - panny
3个回答

9

你是在比较苹果和香蕉。即使使用缓冲读取器(bufferedReader)读取一行数据,读取速度也会比尽可能快地获取数据要慢。请注意,不建议使用available方法,因为它在某些情况下并不准确。当我开始使用密码流(cipher streams)时,我自己发现了这一点。


这非常有趣。从本地文件系统中存在的纯文本文件读取时,使用available是否会存在危险? - Jeremy
@Jeremy,使用available()来为整个流分配缓冲区是不正确的。 - Jeffrey
@Jeffrey 如果你有的话,我很想看看你对此的任何资源。在此之前,我一直很愉快地使用available而没有遇到任何问题。我相信你,但我想知道是否真的存在适用available的情况。 - Jeremy
@Jeremy 阅读 available 的文档。我在上一条语句中更多地引用了文档的第二段。 - Jeffrey
@Jeffrey,我看过了。它说:“对于任意输入流,请勿使用available来确定所需缓冲区的数量。”这并不意味着没有使用available是准确的情况。 - Jeremy
5
“available” 的问题在于它只能返回可用字节数 而不会阻塞。如果您 100% 确定您的“InputStream”缓冲区包含整个文件并且您的“InputStream”将从“available”返回正确的数字,则可以使用它。但是,如果您的文件大于“InputStream”的缓冲区或者您的“InputStream”不返回正确的数字,则使用它将失败。 - Jeffrey

3

FileReader通常与BufferedReader一起使用,因为经常需要逐行读取文件,特别是当文件具有明确定义的记录结构且每个记录对应于一行时。

此外,正如在javadocs中所述,FileReader可以简化处理字符编码和转换的一些工作:

用于读取字符文件的方便类。该类的构造函数假定默认的字符编码和默认的字节缓冲区大小是合适的... FileReader 用于读取字符流。


3
尝试增加BufferedReader缓冲区的大小。例如:
BufferedReader br = new BufferedReader(new FileReader("test"),2000000);

如果您选择正确的缓冲区大小,您将更快地完成操作。
然后,在使用Reader时,您会花费时间填充StringBuilder。如果您需要处理行,则必须逐行读取文件。但是,如果您只需要在字符串中读取文本,则可以使用public int read(char[] cbuf)读取更大的文本块,并将这些块写入初始化为适当大小的StringWriter中。
选择使用InputStreamReader并不取决于性能。通常情况下,当您读取文本数据时,使用Reader,因为使用Reader可以更轻松地处理字符集。
另一个要点是,您的代码如下:
byte[] b = new byte[is.available()];
is.read(b);
String text = new String(b);

这不正确。 文档 中提到:

请注意,虽然一些 InputStream 的实现将返回流中的总字节数,但许多实现并不会这样做。使用该方法的返回值来分配一个旨在容纳此流中所有数据的缓冲区是错误的。

所以请注意,你需要解决这个问题。

手动提供缓冲区大小似乎对我的性能产生了负面影响。 - Jeremy
你的文件有多大?你为你的JVM分配了多少堆空间? - dash1e

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