Java:System.out.println和System.err.println不按顺序

我的System.out.println()System.err.println()调用没有按照我制作的顺序打印到控制台。

 public static void main(String[] args) { for (int i = 0; i < 5; i++) { System.out.println("out"); System.err.println("err"); } } 

这产生:

 out out out out out err err err err err 

而不是交替err 。 为什么是这样?

他们是不同的溪stream,并在不同的时间冲刷。

如果你放

 System.out.flush(); System.err.flush(); 

在你的循环内,它将按预期工作。

为了澄清,输出stream被caching,所以所有的写入到这个内存缓冲区。 经过一段平静之后,他们实际上已经写出来了。

您写入两个缓冲区,然后经过一段时间不活动后,两个缓冲区都被刷新(一个接一个)。

这是由于JVM中的一个特性造成的,除非你使用Marcus A提供的破解方法,否则解决这个问题并不容易。 .flush()在这种情况下起作用,但是解决这个问题的原因要复杂得多。

这里发生了什么?

JVM很聪明,但也很自闭。 当你用Java编程的时候,你不会直接告诉计算机做什么,而是告诉JVM(Java虚拟机)你想要做什么。 而且这样做会更有效率。 你的代码并不是精确的详细指令,在这种情况下,你只需要一个类似于C和C ++的编译器,JVM就会将你的代码作为规范列表进行优化,然后执行。 这是在这里发生的事情 。 Java看到你正在将string推入两个不同的缓冲stream中。 这样做的最有效方法是缓冲所有要输出的string,然后输出。 这发生在一个stream,本质上改变你的代码做这样的事情(小心:伪代码)

 for(int i = 0; i < 5; i++) { out.add(); err.add(); } out.flush(); err.flush(); 

因为这样更有效率,所以这就是JVM所要做的。 在循环中添加.flush()将向JVM发出信号,表示在每个循环中都需要进行刷新,这是上述方法无法改进的。 但是如果你为了解释这个工作原理将会遗漏掉循环,JVM会重新排列你的代码以便最后完成打印,因为这样做效率更高。

 System.out.println("out"); System.out.flush(); System.err.println("err"); System.err.flush(); System.out.println("out"); System.out.flush(); System.err.println("err"); System.err.flush(); 

这个代码总是会被重新组织成这样的:

 System.out.println("out");* System.err.println("err");* System.out.println("out");* System.err.println("err");* System.out.flush(); System.err.flush(); 

因为缓冲许多缓冲区只在刷新它们之后花费很多时间,而不是缓冲所有要缓冲的代码,然后同时刷新所有的代码。

如何解决它

这是代码devise和架构可能发挥作用的地方; 你有点不解决这个问题。 要解决这个问题,你必须使缓冲打印/刷新,缓冲区的打印/刷新比缓冲区更有效,然后刷新。 这很可能会引诱你陷入糟糕的devise。 如果重要的是如何输出有序,我build议你尝试一种不同的方法。 使用.flush()进行循环是破解它的一种方法,但是您仍然窃听JVM的function来为您重新安排和优化代码。

*我无法validation您首先添加的缓冲区是否会首先打印,但最有可能的。

如果您使用的是Eclipse控制台,则似乎有两种不同的现象:
其中一个,如@Gemtastic所描述的,是JVM处理stream,另一个就是Eclipse读取这些stream的方式,正如@DraganBozanovic所提到的。 由于我使用的是Eclipse,因此只能解决JVM问题的由@BillK发布的优雅的flush() solution是不够的。

我最终写了一个名为EclipseTools的助手类, EclipseTools包含以下内容(以及所需的包声明和导入)。 这是一个黑客,但解决了这两个问题:

 public class EclipseTools { private static List<OutputStream> streams = null; private static OutputStream lastStream = null; private static class FixedStream extends OutputStream { private final OutputStream target; public FixedStream(OutputStream originalStream) { target = originalStream; streams.add(this); } @Override public void write(int b) throws IOException { if (lastStream!=this) swap(); target.write(b); } @Override public void write(byte[] b) throws IOException { if (lastStream!=this) swap(); target.write(b); } @Override public void write(byte[] b, int off, int len) throws IOException { if (lastStream!=this) swap(); target.write(b, off, len); } private void swap() throws IOException { if (lastStream!=null) { lastStream.flush(); try { Thread.sleep(200); } catch (InterruptedException e) {} } lastStream = this; } @Override public void close() throws IOException { target.close(); } @Override public void flush() throws IOException { target.flush(); } } /** * Inserts a 200ms delay into the System.err or System.out OutputStreams * every time the output switches from one to the other. This prevents * the Eclipse console from showing the output of the two streams out of * order. This function only needs to be called once. */ public static void fixConsole() { if (streams!=null) return; streams = new ArrayList<OutputStream>(); System.setErr(new PrintStream(new FixedStream(System.err))); System.setOut(new PrintStream(new FixedStream(System.out))); } } 

要使用,只需在代码的开头调用EclipseTools.fixConsole()一次。

基本上,这会用一组自定义的stream来replace两个streamSystem.errSystem.out ,这些stream简单地将其数据转发到原始stream,但要跟踪哪个stream被写入最后。 如果写入的数据stream发生变化,例如System.err.something(...)后跟System.out.something(...) ,则刷新最后一个数据stream的输出并等待200ms Eclipse控制台的时间来完成打印它。

注意:200ms只是一个粗略的初始值。 如果此代码减less,但不能消除您的问题,请将Thread.sleep的延迟从200增加到更高,直到它工作。 或者,如果这个延迟工作,但影响你的代码的性能(如果你经常交替stream),你可以尝试逐渐减less,直到你开始出现错误。

这两个println语句由两个不同的线程处理。 输出再次取决于你运行代码的环境。例如,我在IntelliJ和命令行中每次执行5次以下代码。

 public class Test { public static void main(String[] args) { for (int i = 0; i < 10; i++) { System.out.print("OUT "); System.err.print("ERR "); } } } 

这导致了以下输出:
命令行

 OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR 

的IntelliJ:

 ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR 

我猜不同的环境不同地处理缓冲区。
看到这些stream事实上由不同线程处理的一种方法是在循环中添加一个sleep语句。 您可以尝试改变为睡眠设置的值,并确定这些值是由不同的线程处理的。

 public class Test { public static void main(String[] args) { for (int i = 0; i < 10; i++) { System.out.print("OUT "); System.err.print("ERR "); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } } 

这种情况下的输出结果是

 OUT ERR ERR OUT ERR OUT OUT ERR OUT ERR ERR OUT OUT ERR ERR OUT OUT ERR OUT ERR OUT ERR ERR OUT ERR OUT OUT ERR OUT ERR ERR OUT OUT ERR ERR OUT OUT ERR OUT ERR ERR OUT ERR OUT OUT ERR ERR OUT OUT ERR ERR OUT OUT ERR OUT ERR ERR OUT ERR OUT ERR OUT OUT ERR ERR OUT OUT ERR ERR OUT ERR OUT OUT ERR ERR OUT ERR OUT OUT ERR OUT ERR OUT ERR ERR OUT OUT ERR ERR OUT OUT ERR ERR OUT ERR OUT OUT ERR OUT ERR 

强制它以相同的顺序打印它的一种方法是使用.flush() ,它为我工作。 但它似乎并不是每个人都能得到正确的结果。

由两个不同的线程处理的两个stream可能是我们有时看到由我们使用的一些库打印的ERROR消息的原因,在根据执行顺序应该看到的一些打印语句之前打印。

这是Eclipse中的一个错误 。 看来Eclipse使用单独的线程来读取outerrstream的内容,而没有任何同步。

如果您编译该类并在控制台中执行它(使用经典的java <main class name> ),则顺序与预期相同。