【问题标题】:StringBuffer vs normal printStringBuffer 与普通打印
【发布时间】:2020-04-20 08:27:52
【问题描述】:

与将所有数组项追加到 StringBuffer 然后打印相比,为什么在循环中打印数组项需要更多时间?

在此,我在循环中打印了数组项。

public static void main (String[] args) {
    Scanner scan=new Scanner(System.in);
    int count=scan.nextInt();
    for (int i=0;i<count;i++) {
        int arrlen=scan.nextInt();
        int rotate=scan.nextInt();
        int revarr[]=new int[arrlen];
        for (int j=0;j<arrlen;j++) {
            revarr[(arrlen-rotate+j)%arrlen]=scan.nextInt();
        }
        for (int j:revarr) {
            System.out.print(j+" ");
        }   
        System.out.println();
    }
}

在此,我将数组项附加到 StringBuffer 中,然后打印。

public static void main (String[] args) {
    Scanner scan=new Scanner(System.in);
    int count=scan.nextInt();
    for (int i=0;i<count;i++) {
        int arrlen=scan.nextInt();
        int rotate=scan.nextInt();
        int revarr[]=new int[arrlen];
        for(int j=0;j<arrlen;j++){
            revarr[(arrlen-rotate+j)%arrlen]=scan.nextInt();
        }
        StringBuffer s = new StringBuffer();
        for (int j:revarr){
            s.append(j+" ");
        }   
        System.out.println(s);
    }
}

【问题讨论】:

  • 你的情况下这些字符串有多长?循环有多少次迭代?我们是在谈论数十次迭代还是数百万次迭代?
  • 因为每个打印调用都是同步的并调用底层操作系统。 --- 仅供参考: 如果您使用StringBuilder,您的代码会更快。
  • 这是您使用输出流的次数。在 StringBuffer 的情况下,您只调用一次,但在另一种情况下,您多次调用它,并且每次都贡献一个不错的时间 linit。
  • 每次都调用print,就像去逛街买5包卫生纸一样。但是你只拿一个包裹,走到收银台,付钱,把它搬到停车场。然后你往回走,再拿一个,付钱,把它移到车上,重复。相比:你拿着购物车,把5包卫生纸放进购物车,搬到收银台,一次付款,把所有东西都搬到车上。你真的很惊讶做两件完全不同的事情需要不同的时间吗?换句话说:真正的意义是在这里质疑你的假设......
  • “因为每个打印调用......都会调用底层操作系统” - 这并不完全正确。只有当System.out 的缓冲区填满或调用println 时才会调用操作。

标签: java stringbuffer


【解决方案1】:

可能的原因与 PrintStream 的实现有关,System.out 设置为(默认情况下)。

在Java 11中,创建PrintStream的方法如下:

private static PrintStream newPrintStream(FileOutputStream fos, String enc) {
   if (enc != null) {
        try {
            return new PrintStream(new BufferedOutputStream(fos, 128), true, enc);
        } catch (UnsupportedEncodingException uee) {}
    }
    return new PrintStream(new BufferedOutputStream(fos, 128), true);
}

关键是这样的:

return new PrintStream(new BufferedOutputStream(fos, 128), true, enc);

首先,它创建一个缓冲区大小为 128 字节的BufferedOutputStream。那是很小的。如果您对System.out 进行少量写入,则至少每写入 128 个字节就会有一次缓冲区刷新。

其次,true 参数启用自动刷新。 javadoc 对此进行了如下描述:

“如果为 true,则每当写入字节数组、调用 println 方法之一或写入换行符或字节 ('\n') 时,都会刷新输出缓冲区”

再次,这意味着有更多的冲洗。

为什么这会有所不同?

井冲洗涉及执行fwrite 系统调用以写出字符。系统调用相对昂贵。根据我看到的一些数字,fwrite 系统调用的系统调用开销大​​约为 60 到 350 ns。这不是很大,但是如果您每次循环迭代执行几次而不是每次循环迭代执行一次,并且您重复该操作的时间足够长,那么差异可能会很显着。

还可能存在取决于System.out 连接到什么的开销。例如,如果您正在向控制台写入,那么大量的小写入可能会减慢控制台应用程序的速度。


另一种解释是可能的。您显示的基准代码没有考虑任何可能的 JVM 预热效应。例如,一个例子可能触发了 JIT 编译,而另一个没有。 JIT 编译的开销可能导致前者花费的时间比后者长。

【讨论】:

    【解决方案2】:

    打印到 System.out 是一个比较长的操作,添加到 StringBuffer 很快。 StringBuffer 旨在连接字符串。但是,StringBuffer 已过时。改用 StringBuilder

    【讨论】:

    • “was”是有效词。没有人应该再使用StringBuffer了。
    【解决方案3】:

    在 Java 中输入和输出操作需要很长时间是很常见的。 每次调用 printprintln 时,它都会产生额外的开销,比实际打印更耗时,这就是为什么它实际上要多 一批打印出来的速度更快。
    这是printprintln使用的方法:

        private void write(String s) {
            try {
                synchronized(this) {
                    this.ensureOpen();
                    this.textOut.write(s);
                    this.textOut.flushBuffer();
                    this.charOut.flushBuffer();
                    if (this.autoFlush && s.indexOf(10) >= 0) {
                        this.out.flush();
                    }
                }
            } catch (InterruptedIOException var5) {
                Thread.currentThread().interrupt();
            } catch (IOException var6) {
                this.trouble = true;
            }
    
        }
    

    如您所见,这是一个同步操作。 Java 中的同步阻碍了性能,因为获取和释放锁需要大量时间,并且同步块中的代码没有经过编译器优化。

    如果您使用StringBuilder 而不是StringBuffer,您可以获得更快的时间,因为它不同步并且您的开销会更小。

    【讨论】:

      猜你喜欢
      • 2014-07-19
      • 2013-01-26
      • 2017-07-15
      • 2014-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-27
      • 2015-04-04
      相关资源
      最近更新 更多