【问题标题】:CharBuffer vs. char[]CharBuffer 与 char[]
【发布时间】:2010-09-22 14:29:16
【问题描述】:

是否有任何理由更喜欢CharBuffer 而不是char[]

CharBuffer buf = CharBuffer.allocate(DEFAULT_BUFFER_SIZE);
while( in.read(buf) >= 0 ) {
  out.append( buf.flip() );
  buf.clear();
}

对比

char[] buf = new char[DEFAULT_BUFFER_SIZE];
int n;
while( (n = in.read(buf)) >= 0 ) {
  out.write( buf, 0, n );
}

(其中inReaderout 中的Writer)?

【问题讨论】:

    标签: java io buffer


    【解决方案1】:

    CharBuffer 版本稍微不那么复杂(少一个变量),封装了缓冲区大小处理并使用标准 API。一般来说,我更喜欢这个。

    但是,至少在某些情况下,更喜欢阵列版本仍有一个很好的理由。 CharBuffer 仅在 Java 1.4 中引入,因此如果您要部署到早期版本,则不能使用 Charbuffer(除非您自己滚动/使用反向端口)。

    P.S 如果您使用反向移植,请记住在赶上包含反向移植代码的 “真实” 版本的版本后将其删除。

    【讨论】:

      【解决方案2】:

      您应该避免在最近的 Java 版本中使用CharBuffer#subsequence() 中有一个错误。由于实现混淆了capacityremaining,因此您无法从缓冲区的后半部分获取子序列。我观察到 java 6-0-11 和 6-0-12 中的错误。

      【讨论】:

      • 既然 Java 8 出来了,你有机会更新这个答案吗?
      【解决方案3】:

      我想对这个比较进行小型基准测试。

      下面是我写的课。

      问题是我无法相信 CharBuffer 表现如此糟糕。我做错了什么?

      编辑:自从下面的第 11 条评论以来,我已经编辑了代码和输出时间,整体性能更好,但在时间上仍然存在显着差异。我还尝试了 cmets 中提到的 out2.append((CharBuffer)buff.flip()) 选项,但它比下面代码中使用的 write 选项慢得多。

      结果:(以毫秒为单位的时间)
      字符 [] : 3411
      字符缓冲区:5653

      public class CharBufferScratchBox
      {
          public static void main(String[] args) throws Exception
          {
              // Some Setup Stuff
              String smallString =
                      "1111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000";
      
              StringBuilder stringBuilder = new StringBuilder();
              for (int i = 0; i < 1000; i++)
              {
                  stringBuilder.append(smallString);
              }
              String string = stringBuilder.toString();
              int DEFAULT_BUFFER_SIZE = 1000;
              int ITTERATIONS = 10000;
      
              // char[]
              StringReader in1 = null;
              StringWriter out1 = null;
              Date start = new Date();
              for (int i = 0; i < ITTERATIONS; i++)
              {
                  in1 = new StringReader(string);
                  out1 = new StringWriter(string.length());
      
                  char[] buf = new char[DEFAULT_BUFFER_SIZE];
                  int n;
                  while ((n = in1.read(buf)) >= 0)
                  {
                      out1.write(
                              buf,
                              0,
                              n);
                  }
              }
              Date done = new Date();
              System.out.println("char[]    : " + (done.getTime() - start.getTime()));
      
              // CharBuffer
              StringReader in2 = null;
              StringWriter out2 = null;
              start = new Date();
              CharBuffer buff = CharBuffer.allocate(DEFAULT_BUFFER_SIZE);
              for (int i = 0; i < ITTERATIONS; i++)
              {
                  in2 = new StringReader(string);
                  out2 = new StringWriter(string.length());
                  int n;
                  while ((n = in2.read(buff)) >= 0)
                  {
                      out2.write(
                              buff.array(),
                              0,
                              n);
                      buff.clear();
                  }
              }
              done = new Date();
              System.out.println("CharBuffer: " + (done.getTime() - start.getTime()));
          }
      }
      

      【讨论】:

      • 在我使用 Java 1.6 的 2007 MacBookPro 上,第二个版本仅(!)慢了 35% - 2700ms vs 2000ms。
      • 是的。我上面的时间使用 1.5,而我得到 1.6 的时间更快。你(@Alnitak)报告的大约 35%。
      • 为什么要写(buf.array(),0,n) 而不是写(buf.flip())?
      • 好的,所以我替换了 out2.write(buff.array(),0,n);与 out2.append((CharBuffer)buff.flip());这使得时间比较变得更糟,增加了 135% - 呸!使用 char[] 在这种情况下显然更快。 :)
      • 你的微基准测试的东西太多了。 StringWriter 是在没有参数的情况下分配的,因此它必须自行调整大小。 StringWriter 由 StringBuffer 支持,默认为 16。尝试使用参数 string.length() 分配它。
      【解决方案4】:

      实际上,差异实际上是

      为了读取和写入 5MB 文件 24 次,我使用 Profiler 获取了我的数字。平均而言:

      char[] = 4139 ms
      CharBuffer = 4466 ms
      ByteBuffer = 938 (direct) ms
      

      个别测试多次青睐 CharBuffer。

      我还尝试将基于文件的 IO 替换为内存 IO,并且性能相似。如果您尝试从一个原生流传输到另一个原生流,那么最好使用“直接”ByteBuffer。

      在性能差异小于 10% 的情况下,在实践中,我更喜欢 CharBuffer。它的语法更清晰,无关变量更少,您可以对其进行更直接的操作(即任何需要 CharSequence 的操作)。

      Benchmark is below...它有点错误,因为 BufferedReader 分配在测试方法内部而不是外部...但是,下面的示例允许您隔离 IO 时间并消除字符串或字节流等因素调整其内部内存缓冲区等的大小。

      public static void main(String[] args) throws Exception {
          File f = getBytes(5000000);
          System.out.println(f.getAbsolutePath());
          try {
              System.gc();
              List<Main> impls = new java.util.ArrayList<Main>();
              impls.add(new CharArrayImpl());
              //impls.add(new CharArrayNoBuffImpl());
              impls.add(new CharBufferImpl());
              //impls.add(new CharBufferNoBuffImpl());
              impls.add(new ByteBufferDirectImpl());
              //impls.add(new CharBufferDirectImpl());
              for (int i = 0; i < 25; i++) {
                  for (Main impl : impls) {
                      test(f, impl);
                  }
                  System.out.println("-----");
                  if(i==0)
                      continue; //reset profiler
              }
              System.gc();
              System.out.println("Finished");
              return;
          } finally {
              f.delete();
          }
      }
      static int BUFFER_SIZE = 1000;
      
      static File getBytes(int size) throws IOException {
          File f = File.createTempFile("input", ".txt");
          FileWriter writer = new FileWriter(f);
          Random r = new Random();
          for (int i = 0; i < size; i++) {
              writer.write(Integer.toString(5));
          }
          writer.close();
          return f;
      }
      
      static void test(File f, Main impl) throws IOException {
          InputStream in = new FileInputStream(f);
          File fout = File.createTempFile("output", ".txt");
          try {
              OutputStream out = new FileOutputStream(fout, false);
              try {
                  long start = System.currentTimeMillis();
                  impl.runTest(in, out);
                  long end = System.currentTimeMillis();
                  System.out.println(impl.getClass().getName() + " = " + (end - start) + "ms");
              } finally {
                  out.close();
              }
          } finally {
              fout.delete();
              in.close();
          }
      }
      
      public abstract void runTest(InputStream ins, OutputStream outs) throws IOException;
      
      public static class CharArrayImpl extends Main {
      
          char[] buff = new char[BUFFER_SIZE];
      
          public void runTest(InputStream ins, OutputStream outs) throws IOException {
              Reader in = new BufferedReader(new InputStreamReader(ins));
              Writer out = new BufferedWriter(new OutputStreamWriter(outs));
              int n;
              while ((n = in.read(buff)) >= 0) {
                  out.write(buff, 0, n);
              }
          }
      }
      
      public static class CharBufferImpl extends Main {
      
          CharBuffer buff = CharBuffer.allocate(BUFFER_SIZE);
      
          public void runTest(InputStream ins, OutputStream outs) throws IOException {
              Reader in = new BufferedReader(new InputStreamReader(ins));
              Writer out = new BufferedWriter(new OutputStreamWriter(outs));
              int n;
              while ((n = in.read(buff)) >= 0) {
                  buff.flip();
                  out.append(buff);
                  buff.clear();
              }
          }
      }
      
      public static class ByteBufferDirectImpl extends Main {
      
          ByteBuffer buff = ByteBuffer.allocateDirect(BUFFER_SIZE * 2);
      
          public void runTest(InputStream ins, OutputStream outs) throws IOException {
              ReadableByteChannel in = Channels.newChannel(ins);
              WritableByteChannel out = Channels.newChannel(outs);
              int n;
              while ((n = in.read(buff)) >= 0) {
                  buff.flip();
                  out.write(buff);
                  buff.clear();
              }
          }
      }
      

      【讨论】:

        【解决方案5】:

        不,在这种情况下确实没有理由更喜欢CharBuffer

        不过,一般来说,CharBuffer(和ByteBuffer)可以真正简化 API 并鼓励正确处理。如果您正在设计公共 API,那么绝对值得考虑使用面向缓冲区的 API。

        【讨论】:

          【解决方案6】:

          我认为 CharBuffer 和 ByteBuffer(以及任何其他 xBuffer)旨在实现可重用性,因此您可以 buf.clear() 它们而不是每次都重新分配

          如果您不重复使用它们,就没有充分发挥它们的潜力,而且会增加额外的开销。但是,如果您打算扩展此功能,则将它们保留在那里可能是个好主意

          【讨论】:

          • 缓冲区的全部潜力在于直接缓冲区和轻松更改数据表示的能力。您可以使用 ByteArray.asTYPE() 即时将字节转换为数字或字符串。您也可以更改字节顺序。
          【解决方案7】:

          如果这是您对缓冲区所做的唯一事情,那么在这种情况下,数组可能是更好的选择。

          CharBuffer 上有很多额外的 chrome,但在这种情况下它们都无关紧要 - 只会减慢一小部分速度。

          如果您需要让事情变得更复杂,您可以随时重构。

          【讨论】:

          • 这适合当前的需求。需求变化。
          • 使用标准实现(我敢说模式 :-) )可以减少容易出错的代码。并不意味着使用数组是错误的或有问题的,只是更有可能如此。
          • 郑重声明,我不同意“第一次做对”。第一次针对这个需求做对,同时有信心当需求发生变化时我可以改变事情,如果你能让环境支持这一理念,那就更好了。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多