catch和finally块的输出顺序不一致
catch和finally块的输出顺序不一致
为什么我的System.out.println()
和System.err.println()
调用不按照我调用它们的顺序打印到控制台上?\n
public static void main(String[] args) { for (int i = 0; i < 5; i++) { System.out.println("out"); System.err.println("err"); } }
\n这段代码输出结果为:\n
out out out out out err err err err err
\n而不是交替出现的out
和err
。为什么会这样?
问题出现的原因:
在使用Eclipse控制台的情况下,有两种不同的现象存在。一种是由JVM处理流的方式引起的,另一种是Eclipse读取这些流的方式。由于我使用的是Eclipse,所以仅仅解决JVM问题的flush()解决方案是不够的。
解决方法:
我最终编写了一个名为EclipseTools的辅助类来解决这个问题。该类包含以下内容:
public class EclipseTools { private static Liststreams = 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); } public void write(int b) throws IOException { if (lastStream!=this) swap(); target.write(b); } public void write(byte[] b) throws IOException { if (lastStream!=this) swap(); target.write(b); } 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; } public void close() throws IOException { target.close(); } public void flush() throws IOException { target.flush(); } } public static void fixConsole() { if (streams!=null) return; streams = new ArrayList (); System.setErr(new PrintStream(new FixedStream(System.err))); System.setOut(new PrintStream(new FixedStream(System.out))); } }
使用时,只需在代码的开头调用EclipseTools.fixConsole()一次即可。
基本上,这个辅助类将System.err和System.out这两个流替换为一个自定义的流集合,这些流将数据转发给原始流,并跟踪最后写入的流。如果写入的流发生变化,例如System.err.something(...)后跟System.out.something(...),它会刷新上一个流的输出,并等待200毫秒,以便Eclipse控制台有足够的时间将其完全打印出来。
注意:200毫秒只是一个粗略的初始值。如果这段代码减少了问题但没有完全解决,可以将Thread.sleep中的延迟从200增加到更高的值,直到解决问题。或者,如果这个延迟有效但影响了代码的性能(如果频繁地切换流),可以逐渐减少它,直到出现错误。
在Java中,catch块和finally块的输出顺序不一致是由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发出信号,要求每次循环都要进行flush,这在上述方法中无法优化。但是,如果仅仅是为了解释这个问题的原理,你可以省略循环,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为你重新排列和优化代码的特性。
在这篇文章中,我们将讨论Java中的一个问题:catch块和finally块的输出顺序不一致。首先,让我们看一下这个问题出现的原因以及如何解决。
问题的根源在于catch块和finally块使用了不同的输出流,并且在不同的时间进行刷新。输出流会将内容缓存在内存缓冲区中,当一定时间没有输出时,这些内容就会被写入到实际的输出流中。因此,当我们在代码中使用了多个输出流时,输出的顺序可能会出现问题。
为了解决这个问题,我们可以在代码的循环中添加以下代码:
System.out.flush(); System.err.flush();
这样做可以强制刷新输出流,使得输出按照我们期望的顺序进行。需要注意的是,这个解决方法仅适用于Java 1.7及以上版本。
有人可能会认为这个问题并不存在,因为根据Java的规定,System.out和System.err应该在每个换行符处自动刷新。然而,实际情况是在某些情况下,这种自动刷新并不起作用,需要手动刷新输出流。
有人在Stack Overflow上提到了这个问题,并给出了一个解决方法,即在代码中添加刷新语句。然而,这个答案被指出是错误的,因为根据Java的规定,自动刷新应该已经在代码中的println()方法中执行了。尽管如此,添加刷新语句却能改变输出的行为。
在使用IntelliJ时,我也遇到了这个问题。根据Bill的建议,在写入错误后加入了System.err.flush(),问题得到了解决。感谢他的建议!
这个问题的原因是不同的输出流在不同的时间刷新,解决方法是手动刷新输出流。