我的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而不是交替使用out和err。为什么会这样呢?
发布于 2009-12-11 03:23:11
它们是不同的流,并且在不同的时间刷新。
如果你把
System.out.flush();
System.err.flush();在您的循环中,它将按预期工作。
为了清楚起见,输出流被缓存,所以所有的写操作都会进入这个内存缓冲区。在一段时间的安静之后,它们实际上被写出来了。
您写入两个缓冲区,然后在一段时间不活动之后,它们都会被刷新(一个接一个)。
发布于 2015-07-07 17:15:36
这是由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();因为这样效率更高,所以这就是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()的for循环是破解它的一种方法,但是您仍然在破解JVM虚拟机的特性来重新安排和优化您的代码。
*我无法验证您添加到first的缓冲区是否总是首先打印,但它很可能会。
发布于 2014-05-17 09:42:08
如果您正在使用Eclipse控制台,那么似乎有两种不同的现象在起作用:
一个是@Gemtastic对流的JVM处理,另一个是@DraganBozanovic提到的读取这些流的方式。因为我使用的是Eclipse,所以@BillK发布的优雅的flush()-solution是不够的,它只解决了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.err和System.out这两个流,这些流只是将它们的数据转发到原始流,但会跟踪哪个流是最后写入的。如果写入的流发生更改,例如,在System.out.something(...)之后是System.err.something(...),它将刷新最后一个流的输出,并等待200ms,以便Eclipse控制台有时间完成打印。
注意: 200ms只是一个粗略的初始值。如果此代码减少了,但不能消除问题,请将Thread.sleep中的延迟从200增加到更高的值,直到它起作用。或者,如果此延迟有效,但会影响代码的性能(如果您经常交替流),您可以尝试逐渐减少延迟,直到开始出现错误。
https://stackoverflow.com/questions/1883321
复制相似问题