为什么System.out.println这么慢?

这是所有编程语言的共同点吗? 进行多次打印然后使用println似乎更快但将所有内容移动到字符串并且只是打印似乎最快。 为什么?

编辑:例如,Java可以在不到一秒的时间内找到高达100万的所有素数 – 但是打印然后在他们自己的println上全部打印可能需要几分钟! 打印高达100亿小时!

EX:

package sieveoferatosthenes; public class Main { public static void main(String[] args) { int upTo = 10000000; boolean primes[] = new boolean[upTo]; for( int b = 0; b < upTo; b++ ){ primes[b] = true; } primes[0] = false; primes[1] = false; int testing = 1; while( testing <= Math.sqrt(upTo)){ testing ++; int testingWith = testing; if( primes[testing] ){ while( testingWith = upTo){ } else{ primes[testingWith] = false; } } } } for( int b = 2; b < upTo; b++){ if( primes[b] ){ System.out.println( b ); } } } } 

println并不慢,它是由托管操作系统提供的与控制台连接的底层PrintStream

您可以自己检查:比较将大文本文件转储到控制台,并将相同的文本文件传输到另一个文件中:

 cat largeTextFile.txt cat largeTextFile.txt > temp.txt 

读取和写入类似于文件大小(O(n)),唯一的区别是目标不同(控制台与文件相比)。 这与System.out基本相同。


底层操作系统操作(在控制台窗口上显示字符)很慢,因为

  1. 必须将字节发送到控制台应用程序(应该非常快)
  2. 每个char都必须使用(通常)一个真正的字体来渲染(这很慢,关闭抗锯齿可以提高性能,顺便说一句)
  3. 可能必须滚动显示的区域以便向可见区域附加新行(最佳情况:位块传输操作,最坏情况:重新呈现完整文本区域)

我相信这是因为缓冲 。 文章的引用:

缓冲的另一方面涉及文本输出到终端窗口。 默认情况下,System.out(PrintStream)是行缓冲的,这意味着在遇到换行符时会刷新输出缓冲区。 这对于交互性非常重要,您希望在实际输入任何输入之前显示输入提示。

从维基百科解释缓冲区的引用:

在计算机科学中,缓冲区是一个内存区域,用于在数据从一个地方移动到另一个地方时临时保存数据。 通常,数据存储在缓冲区中,因为它是从输入设备(例如鼠标)检索的,或者在将数据发送到输出设备(例如扬声器)之前

 public void println() 

通过写行分隔符字符串来终止当前行。 行分隔符字符串由系统属性line.separator定义,不一定是单个换行符(’\ n’)。

因此,当你执行println时,缓冲区会被刷新,这意味着必须分配新内存等,这会使打印速度变慢。 您指定的其他方法需要较少的缓冲区刷新因此更快。

System.out是一个静态PrintStream类。 除其他外, PrintStream还有一些您可能非常熟悉的方法,比如print()println()等。

输入和输出操作需要很长时间才是Java的独特之处。 “长。” 打印或写入PrintStream需要几分之一秒,但这个打印的超过100亿个实例可以相当多!

这就是为什么你的“将所有内容移动到字符串”是最快的。 您构建了巨大的String,但只打印一次 。 当然,这是一个巨大的打印,但你花时间实际打印,而不是与print()println()相关的开销。

正如Dvd Prd所提到的,字符串是不可改变的。 这意味着无论何时将新String分配给旧String但重用引用,实际上都会销毁对旧String的引用并创建对新String的引用。 因此,通过使用可变的StringBuilder类,您可以使整个操作更快。 这将减少与构建您最终打印的字符串相关的开销。

看看我的System.out.println替换 。

默认情况下,System.out.print()只是行缓冲,并且与Unicode处理有很多相关的工作。 由于其缓冲区大小较小,System.out.println()不适合在批处理模式下处理许多重复输出。 每条线都立即冲洗。 如果您的输出主要是基于ASCII的,那么通过删除与Unicode相关的活动,总体执行时间会更好。

如果您要打印到控制台窗口而不是文件,那将是杀手。

每个角色都必须被绘制,并且在每一行上都必须滚动整个窗口。 如果窗口部分与其他窗口重叠,则还必须进行裁剪。

这将比你的程序所做的更多周期。

通常这不是一个糟糕的代价,因为控制台输出应该是为了您的阅读乐趣:)

你遇到的问题是显示到屏幕是非常昂贵的,特别是如果你有一个图形的Windows / X-windows环境(而不是纯文本终端)只是为了渲染一个字体的字体比计算你昂贵得多是做。 当您将数据发送到屏幕的速度超过显示数据时,它会缓冲数据并快速阻止。 即使写入文件与计算相比也很重要,但它比屏幕上显示的速度快10倍到100倍。

BTW:math.sqrt()非常昂贵,并且使用循环比使用模数慢得多,即%来确定数字是否是倍数。 BitSet可以比boolean []高8倍

如果我将输出转储到文件,它很快,但写入控制台很慢,如果我写入控制台,写入文件的数据需要大约相同的时间。

 Took 289 ms to examine 10,000,000 numbers. Took 149 ms to toString primes up to 10,000,000. Took 306 ms to write to a file primes up to 10,000,000. Took 61,082 ms to write to a System.out primes up to 10,000,000. time cat primes.txt real 1m24.916s user 0m3.619s sys 0m12.058s 

代码

 int upTo = 10*1000*1000; long start = System.nanoTime(); BitSet nonprimes = new BitSet(upTo); for (int t = 2; t * t < upTo; t++) { if (nonprimes.get(t)) continue; for (int i = 2 * t; i <= upTo; i += t) nonprimes.set(i); } PrintWriter report = new PrintWriter("report.txt"); long time = System.nanoTime() - start; report.printf("Took %,d ms to examine %,d numbers.%n", time / 1000 / 1000, upTo); long start2 = System.nanoTime(); for (int i = 2; i < upTo; i++) { if (!nonprimes.get(i)) Integer.toString(i); } long time2 = System.nanoTime() - start2; report.printf("Took %,d ms to toString primes up to %,d.%n", time2 / 1000 / 1000, upTo); long start3 = System.nanoTime(); PrintWriter pw = new PrintWriter(new BufferedOutputStream(new FileOutputStream("primes.txt"), 64*1024)); for (int i = 2; i < upTo; i++) { if (!nonprimes.get(i)) pw.println(i); } pw.close(); long time3 = System.nanoTime() - start3; report.printf("Took %,d ms to write to a file primes up to %,d.%n", time3 / 1000 / 1000, upTo); long start4 = System.nanoTime(); for (int i = 2; i < upTo; i++) { if (!nonprimes.get(i)) System.out.println(i); } long time4 = System.nanoTime() - start4; report.printf("Took %,d ms to write to a System.out primes up to %,d.%n", time4 / 1000 / 1000, upTo); report.close();