【问题标题】:PrintWriter vs PrintStream vs OutputStreamWriter timecostsPrintWriter vs PrintStream vs OutputStreamWriter 时间成本
【发布时间】:2013-08-28 04:17:47
【问题描述】:

如您所知,我们在 java 中有多种工具可以将数据写入流。
在此示例代码中,我按运行时对它们进行了比较。
有人可以准确地解释一下吗?谢谢。
这是代码:

import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;

public class IOtests
{

public static void main(String[] args) throws Exception
{
    char[] chars = new char[100];
    byte[] bytes = new byte[100];
    for (int i = 0; i < 100; i++)
    {
        chars[i] = (char) i;
        bytes[i] = (byte) i;
    }
    OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(
            "output.txt"));
    long a = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++)
        for (char j : chars)
            out.write(j);
    System.out.println("OutputStreamWriter writing characters: "
            + (System.currentTimeMillis() - a));
    out = new OutputStreamWriter(new FileOutputStream("output.txt"));
    a = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++)
        for (byte j : bytes)
            out.write(j);
    System.out.println("OutputStreamWriter writing bytes: "
            + (System.currentTimeMillis() - a));
    PrintStream out1 = new PrintStream("output.txt");
    a = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++)
        for (char j : chars)
            out1.write(j);
    System.out.println("PrintStream writing characters: "
            + (System.currentTimeMillis() - a));
    out1 = new PrintStream("output.txt");
    a = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++)
        for (byte j : bytes)
            out1.write(j);
    System.out.println("PrintStream writing bytes: "
            + (System.currentTimeMillis() - a));
    PrintWriter out2 = new PrintWriter("output.txt");
    a = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++)
        for (char j : chars)
            out2.write(j);
    System.out.println("PrintWriter writing characters: "
            + (System.currentTimeMillis() - a));
    out1 = new PrintStream("output.txt");
    a = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++)
        for (byte j : bytes)
            out2.write(j);
    System.out.println("PrintWriter writing bytes: "
            + (System.currentTimeMillis() - a));
}

}

结果:

OutputStreamWriter 写入字符:4141
OutputStreamWriter 写入字节数:3546
PrintStream 写入字符:86516
PrintStream 写入字节数:70484
PrintWriter 书写字符:938
PrintWriter 写入字节数:2484

请注意,所有时间都以毫秒为单位。

【问题讨论】:

  • 鉴于您从不关闭输出,它可以全部被缓冲。此外,您没有进行任何 JIT 预热,也没有执行任何垃圾收集等。哦,“PrintWriter 写入字节”是用词不当,因为它只写入 字符。您刚刚获得了隐式字节到 int 的转换。此外,在大多数明智的代码中一次写入单个字节或字符是不现实的——你会使用采用 byte[]char[]String 的重载。
  • 您的计时数字非常可疑。有关如何在 Java 中编写正确基准的信息,请参阅 this thread
  • @TedHopp 考虑到所有这些建议,写入磁盘上的文件本身就是一个不可预测的过程。如果在 *nix 上,可以使用 /dev/null 作为相对可预测的接收器;在 Windows 上,天知道是什么。
  • 回答 cmets。我在其上运行代码的系统非常旧,这就是数量众多的原因。而且我只是想比较,不想对精确的基准测试如此技术化。在您的系统上运行此代码。运行几次后,您会看到比例几乎相同。
  • @MarkoTopolnik - 是的。 OP 可以使用内存中的输出接收器,或者(更好)来自 Apache Commons 的 NullOutputStream

标签: java io stream iostream


【解决方案1】:

我已将您的问题简化为本质:

public class Test {
  static byte[] bytes = new byte[10_000_000];
  static {
    for (int i = 0; i < bytes.length; i++) bytes[i] = (byte) (i%100+32);
  }
  public static void main(String[] args) throws Exception {
    writer(true);
    writer(false);
    stream(true);
    stream(false);
  }

  static void writer(boolean flush) throws IOException {
    Writer out = new FileWriter("output.txt");
    long a = System.currentTimeMillis();
    for (byte j : bytes) {
      out.write(j);
      if (flush) out.flush();
    }
    out.close();
    System.out.println("FileWriter with" + (flush? "":"out") + " flushing: " +
        (System.currentTimeMillis() - a));
  }
  static void stream(boolean flush) throws IOException {
    OutputStream out = new FileOutputStream("output.txt");
    long a = System.currentTimeMillis();
    for (byte j : bytes) {
      out.write(j);
      if (flush) out.flush();
    }
    out.close();
    System.out.println("FileOutputStream with" + (flush? "":"out") + " flushing: " +
        (System.currentTimeMillis() - a));
  }
}

注意事项:

  • 完成后正确关闭资源;
  • 双循环替换为单循环,但数组更大;
  • 避免编写控制字符来规避自动刷新行为;
  • 只使用字节数组,因为在所有情况下您只测试一种方法:write(int)。因此,无论您使用的是字节还是字符都没有区别;
  • 删除了除 FileWriterFileOutputStream 之外的所有内容,因为所有其他情况都归结为这两个;
  • 以两种模式测试写入器和输出流:每次写入后刷新,直到关闭才刷新。

现在,当你运行它时,你会得到如下输出:

FileWriter with flushing: 28235
FileWriter without flushing: 828
FileOutputStream with flushing: 23984
FileOutputStream without flushing: 23641

那么,教训是什么?

  • 所有写入器都被缓冲,因为它们在内部委托给 StreamEncoder,而 StreamEncoder 本身就是缓冲的;
  • FileOutputStream 未缓冲;
  • 非缓冲逐字节写入非常慢。

良好的做法要求您始终进行缓冲写入:使用缓冲接收器,或在您身边维护一个显式缓冲区。

【讨论】:

  • 真的我无法理解 PrintWriter、PrintStream 和 OutputStreamWriter 之间的区别,并且使用文件只是一个示例,因为我只需要编写这么多字符/字节来比较操作速率和文件作为流的接收器非常合适。因此,如果不考虑不使用缓冲区写入文件很慢这一事实,假设我正在写入终端。现在我必须使用哪个工具?
  • 接收器是什么并不重要:无缓冲 I/O 很慢,因为它涉及每个字节的系统调用。因此,这些问题仅在您写入某些“虚拟”流时才适用,该流不涉及 JVM 本身之外的任何操作。例如,StringWriterByteArrayOutputStream
  • PrintWriter vs. PrintStream,后者是遗留类。写入字符数据时应始终首选Writer,写入二进制数据时应始终首选OutputStream
猜你喜欢
  • 1970-01-01
  • 2012-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多