Java:System.out.println和System.err.println乱序

Nic*_*ner 52 java console system

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

这会产生:

out
out
out
out
out
err
err
err
err
err
Run Code Online (Sandbox Code Playgroud)

而不是交替outerr.为什么是这样?

Bil*_*l K 36

它们是不同的流,并在不同的时间刷新.

如果你放

System.out.flush();
System.err.flush();
Run Code Online (Sandbox Code Playgroud)

在你的循环中,它将按预期工作.

为了澄清,输出流被缓存,因此所有写入都会进入此内存缓冲区.经过一段时间的安静,他们实际上写出来了.

你写入两个缓冲区,然后在一段时间不活动后,它们都被刷新(一个接一个).

  • 这不起作用.将这些flush命令放入循环后,没有任何改变.使用Java 1.7. (20认同)
  • Stack Overflow主题已经回答了[Java:同步标准输出和标准错误](http://stackoverflow.com/questions/6121786/java-synchronizing-standard-out-and-standard-error) (4认同)
  • 这个答案实际上是不正确的,因为`System.out`和`System.err`应该在每个换行符处自动刷新,这显然是因为调用使用`println()`.所以这不应该有所作为. (3认同)
  • 你应该使用这个词很多......你真的尝试过吗? (2认同)

Gem*_*tic 29

这是由JVM中的一个功能引起的,除非你制作一个像Marcus A提供的那样的黑客攻击.它并不是那么容易解决..flush()在这种情况下的工作,但其原因是更复杂的解决方案.

这里发生了什么?

当您使用Java编程时,您并没有直接告诉计算机该做什么,而是告诉JVM(Java虚拟机)您希望它做什么.它会这样做,但以更有效的方式.您的代码不是详细的详细说明,在这种情况下,您只需要像C和C++这样的编译器,JVM将您的代码作为规范列表,用于优化然后执行的操作.这就是这里发生的事情.Java看到你正在将字符串推送到两个不同的缓冲流中.最有效的方法是缓冲您希望输出流的所有字符串,然后输出它.这发生在当时的一个流,本质上转换你的代码做这样的事情(小心:伪代码):

for(int i = 0; i < 5; i++) {
    out.add();
    err.add();
}
out.flush();
err.flush();
Run Code Online (Sandbox Code Playgroud)

因为这样效率更高,所以这就是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();
Run Code Online (Sandbox Code Playgroud)

此代码将始终重新组织为以下内容:

System.out.println("out");*
System.err.println("err");*
System.out.println("out");*
System.err.println("err");*
System.out.flush();
System.err.flush();
Run Code Online (Sandbox Code Playgroud)

因为缓冲许多缓冲区只是为了在冲洗它们之后需要花费更多的时间而不是缓冲所有要缓冲的代码,然后同时将它们全部冲洗掉.

如何解决它

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

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


Mar*_* A. 9

如果你正在使用Eclipse控制台,似乎有两种不同的现象正在发挥作用:
一,正如@Gemtastic所描述的那样,是JVM 对流的处理,另一种是Eclipse读取这些流的方式,如@DraganBozanovic所述.由于我使用的是Eclipse,flush()@ BillK发布的优雅解决方案,仅解决JVM问题,是不够的.

我最后写了一个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)));
    }
}
Run Code Online (Sandbox Code Playgroud)

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

基本上,这将替换两个流System.errSystem.out使用一组自定义流,这些流只是将其数据转发到原始流,但会跟踪哪个流写入最后.如果写入的流发生更改(例如a System.err.something(...)后跟a)System.out.something(...),则会刷新最后一个流的输出并等待200ms,以便Eclipse控制台有时间完成打印.

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