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

时间:2009-12-10 19:20:58

标签: 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");
    }
}

这会产生:

out
out
out
out
out
err
err
err
err
err

而不是交替使用outerr。这是为什么?

7 个答案:

答案 0 :(得分:36)

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

如果你把

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

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

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

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

答案 1 :(得分:27)

这是由JVM中的一项功能引起的,除非你制作一个hack,例如 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();

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

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

如何解决

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

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

答案 2 :(得分:9)

如果您正在使用Eclipse控制台,似乎有两种不同的现象在起作用:一,如@Gemtastic所述,是JVM对流的处理,另一种是Eclipse的方式如@DraganBozanovic所述,读取这些流。由于我使用Eclipse,@BillK发布的优雅flush() - 解决方案仅解决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)));
    }
}

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

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

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

答案 3 :(得分:2)

两个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 

我猜不同的环境会以不同的方式处理缓冲区 查看这些流由不同线程处理的一种方法是在循环中添加sleep语句。您可以尝试更改为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(),这对我有用。但看起来并非每个人都能用它获得正确的结果。

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

答案 4 :(得分:1)

这是bug in Eclipse。似乎Eclipse使用单独的线程来读取outerr流的内容而没有任何同步。

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

答案 5 :(得分:0)

我已经使用线程按以下顺序依次打印System.out和System.err的输出:

    for(int i = 0; i< 5; i++){
        try {
            Thread.sleep(100);
            System.out.print("OUT");
            Thread.sleep(100);
            System.err.print("ERR");
        }catch (InterruptedException ex){
            System.out.println(ex.getMessage());
        }
    }

答案 6 :(得分:0)

专门在Eclipse中,您现在可以在控制台中使用Eclipse 2019-09同步标准和错误输出。

  

Eclipse控制台视图当前无法确保混合标准和错误输出的显示顺序与运行过程产生的顺序相同。

     

对于Java应用程序,启动配置的“通用选项卡”现在提供了合并标准和错误输出的选项。
  这样可以确保标准和错误输出的显示顺序与产生时的顺序相同,但同时会禁用错误输出的单独着色。

https://www.eclipse.org/eclipse/news/4.13/images/merge-process-output.png