【问题标题】:Should I buffer the InputStream or the InputStreamReader?我应该缓冲 InputStream 还是 InputStreamReader?
【发布时间】:2011-03-28 09:52:36
【问题描述】:

以下两种缓冲方法有什么区别(如果有的话)?

Reader r1 = new BufferedReader(new InputStreamReader(in, "UTF-8"), bufferSize);
Reader r2 = new InputStreamReader(new BufferedInputStream(in, bufferSize), "UTF-8");

【问题讨论】:

    标签: java buffering java-io bufferedreader bufferedinputstream


    【解决方案1】:

    r1 效率更高。 InputStreamReader 本身没有大缓冲区。 BufferedReader 可以设置为比InputStreamReader 具有更大的缓冲区。 r2 中的 InputStreamReader 将成为瓶颈。

    简而言之:您应该通过漏斗而不是瓶子来读取数据。


    更新:这是一个小基准程序,只需复制'n'粘贴'n'运行即可。您无需准备文件。

    package com.stackoverflow.q3459127;
    
    import java.io.BufferedInputStream;
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.Reader;
    
    public class Test {
    
        public static void main(String... args) throws Exception {
    
            // Init.
            int bufferSize = 10240; // 10KB.
            int fileSize = 100 * 1024 * 1024; // 100MB.
            File file = new File("/temp.txt");
    
            // Create file (it's also a good JVM warmup).
            System.out.print("Creating file .. ");
            BufferedWriter writer = null;
            try {
                writer = new BufferedWriter(new FileWriter(file));
                for (int i = 0; i < fileSize; i++) {
                    writer.write("0");
                }
                System.out.printf("finished, file size: %d MB.%n", file.length() / 1024 / 1024);
            } finally {
                if (writer != null) try { writer.close(); } catch (IOException ignore) {}
            }
    
            // Read through funnel.
            System.out.print("Reading through funnel .. ");
            Reader r1 = null;        
            try {
                r1 = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"), bufferSize);
                long st = System.nanoTime();
                for (int data; (data = r1.read()) > -1;);
                long et = System.nanoTime();
                System.out.printf("finished in %d ms.%n", (et - st) / 1000000);
            } finally {
                if (r1 != null) try { r1.close(); } catch (IOException ignore) {}
            }
    
            // Read through bottle.
            System.out.print("Reading through bottle .. ");
            Reader r2 = null;        
            try {
                r2 = new InputStreamReader(new BufferedInputStream(new FileInputStream(file), bufferSize), "UTF-8");
                long st = System.nanoTime();
                for (int data; (data = r2.read()) > -1;);
                long et = System.nanoTime();
                System.out.printf("finished in %d ms.%n", (et - st) / 1000000);
            } finally {
                if (r2 != null) try { r2.close(); } catch (IOException ignore) {}
            }
    
            // Cleanup.
            if (!file.delete()) System.err.printf("Oops, failed to delete %s. Cleanup yourself.%n", file.getAbsolutePath());
        }
    
    }
    

    在我的 Latitude E5500 上使用Seagate Momentus 7200.3 硬盘的结果:

    创建文件..完成,文件大小:99 MB。 通过漏斗阅读..在 1593 毫秒内完成。 通过瓶子阅读..在 7760 毫秒内完成。

    【讨论】:

    • 如果底层 InputStream 是 FileInputStream,两个 Reader 会在整个读取过程中执行不同数量的磁盘读取吗?
    • 我使用 perfmon 检查了它,我没有看到明显的差异。我会尽快更新答案以包含基准代码 sn-p。
    • 包名大赞:)
    • 为什么不缓冲磁盘读取呢?如果不这样做,inputStream 是否必须对每个字节的源进行读取调用?我不明白 BDKosher 对磁盘读取的担忧是如何证明的,似乎缓冲 InputStream 的磁盘读取应该更少。 BufferedReader reader = new BufferedReader(new InputStreamReader(new BufferedInputSream(inputStream), "UTF-8"));
    【解决方案2】:

    r1 在读取基于行的流时也更方便,因为BufferedReader 支持readLine 方法。您不必将内容逐个读入 char 数组缓冲区或字符。但是,您必须将 r1 强制转换为 BufferedReader 或将该类型显式用于变量。

    我经常用这个代码sn-p:

    BufferedReader br = ...
    String line;
    while((line=br.readLine())!=null) {
      //process line
    }
    

    【讨论】:

      【解决方案3】:

      针对上述评论中 Ross Studtman 的问题(但也与 OP 相关):

      BufferedReader reader = new BufferedReader(new InputStreamReader(new BufferedInputSream(inputStream), "UTF-8"));
      

      BufferedInputStream 是多余的(并且可能由于多余的复制而损害性能)。这是因为BufferedReader 通过调用InputStreamReader.read(char[], int, int) 以大块的形式从InputStreamReader 请求字符,而后者又(通过StreamDecoder)调用InputStream.read(byte[], int, int) 从底层InputStream 中读取一大块字节。

      您可以通过运行以下代码来说服自己:

      new BufferedReader(new InputStreamReader(new ByteArrayInputStream("Hello world!".getBytes("UTF-8")) {
      
          @Override
          public synchronized int read() {
              System.err.println("ByteArrayInputStream.read()");
              return super.read();
          }
      
          @Override
          public synchronized int read(byte[] b, int off, int len) {
              System.err.println("ByteArrayInputStream.read(..., " + off + ", " + len + ')');
              return super.read(b, off, len);
          }
      
      }, "UTF-8") {
      
          @Override
          public int read() throws IOException {
              System.err.println("InputStreamReader.read()");
              return super.read();
          }
      
          @Override
          public int read(char[] cbuf, int offset, int length) throws IOException {
              System.err.println("InputStreamReader.read(..., " + offset + ", " + length + ')');
              return super.read(cbuf, offset, length);
          }
      
      }).read(); // read one character from the BufferedReader
      

      您将看到以下输出:

      InputStreamReader.read(..., 0, 8192)
      ByteArrayInputStream.read(..., 0, 8192)
      

      这表明BufferedReaderInputStreamReader 请求大量字符,而InputStreamReader 又向底层InputStream 请求大量字节。

      【讨论】:

      • 如果您使用BufferedInputStream,它会以大块的形式从InputStream 请求数据,并从其缓冲区中补充Readers 的较小请求。这不是“多余的”。
      • @EJP:我的示例 sn-p 中的 BufferedInputStream(我的答案中的第一个代码块)是多余的,因为 BufferedReader 请求来自 InputStreamReader 的大块,而后者又请求大块来自底层InputStream 的块。在InputStreamReader 和底层InputStream 之间插入BufferedInputStream 只会增加开销,而不会带来任何性能提升。
      【解决方案4】:

      FWIW,如果您在 Java 8 中打开一个文件,您可以使用Files.newBufferedReader(Path)。我不知道性能与此处描述的其他解决方案相比如何,但至少它推动了决定将什么构造缓冲到 JDK 中。

      【讨论】:

        猜你喜欢
        • 2015-05-10
        • 2011-02-17
        • 2011-02-07
        • 2011-03-12
        • 2023-03-07
        • 2010-10-13
        • 1970-01-01
        • 1970-01-01
        • 2017-07-20
        相关资源
        最近更新 更多