it-swarm.com.de

Java: System.out.println und System.err.println außer Betrieb

Meine System.out.println()- und System.err.println()-Aufrufe werden nicht in der Reihenfolge in der Reihenfolge gedruckt, in der ich sie mache.

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

Dies erzeugt:

out
out
out
out
out
err
err
err
err
err

Anstelle von out und err. Warum ist das?

45
Nick Heiner

Sie sind verschiedene Streams und werden zu unterschiedlichen Zeiten gespült.

Wenn Sie setzen 

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

in Ihrer Schleife funktioniert es wie erwartet.

Zur Verdeutlichung werden die Ausgabeströme zwischengespeichert, sodass der gesamte Schreibvorgang in diesen Speicherpuffer geht. Nach einer Ruhepause werden sie tatsächlich ausgeschrieben.

Sie schreiben in zwei Puffer, dann werden beide nach einer bestimmten Zeit der Inaktivität geleert (einer nach dem anderen).

37
Bill K

Dies wird durch ein Feature in der JVM verursacht. Wenn Sie nicht einen Hack wie den von Marcus A. machen, ist es nicht so einfach, dieses Problem zu umgehen. Die .flush() funktioniert in diesem Fall, aber der Grund dafür ist viel komplizierter.

Was passiert hier?

Wenn Sie in Java programmieren, sagen Sie dem Computer nicht direkt, was er tun soll, sondern der JVM (Java Virtual Machine), was Sie tun sollen. Und das wird es tun, aber auf effizientere Weise. Ihr Code ist keine exakten detaillierten Anweisungen. In diesem Fall benötigen Sie nur einen Compiler wie in C und C++. Die JVM verwendet Ihren Code als Spezifikationsliste für das, was er optimieren soll und dann tun soll. Das ist was hier passiert . Java sieht, dass Sie Strings in zwei verschiedene Pufferströme verschieben. Am effizientesten ist dies, indem Sie alle Zeichenfolgen zwischenspeichern, die die Streams ausgeben sollen, und diese dann ausgeben. Dies geschieht zu einem Zeitpunkt in einem Stream, der im Wesentlichen den Code umwandelt, indem er Folgendes ausführt: (Achtung: Pseudocode) :

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

Da dies effizienter ist, wird dies stattdessen von der JVM ausgeführt. Durch Hinzufügen der .flush() in der Schleife wird der JVM signalisiert, dass in jeder Schleife ein Flush ausgeführt werden muss, was mit der oben genannten Methode nicht verbessert werden kann. Wenn Sie jedoch aus Gründen der Erklärung, wie dies funktioniert, die Schleife ausgelassen haben, ordnet die JVM Ihren Code neu an, damit der Druckvorgang zuletzt ausgeführt wird, da dies effizienter ist.

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();

Dieser Code wird immer wie folgt umstrukturiert:

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

Weil das Puffern vieler Puffer, nur um sie direkt danach zu leeren, viel mehr Zeit erfordert, als den gesamten zu puffernden Code zu puffern und dann alles gleichzeitig zu leeren.

Wie man es löst

Hier können Code-Design und Architektur ins Spiel kommen. Sie lösen das irgendwie nicht. Um dies zu umgehen, müssen Sie das Drucken/Flush, Buffer Print/Flush effizienter puffern, als alle Puffer und dann das Flushen. Dies wird Sie höchstwahrscheinlich in schlechtes Design locken. Wenn es Ihnen wichtig ist, wie Sie es ordnungsgemäß ausgeben, sollten Sie einen anderen Ansatz wählen. For-Looping mit .flush() ist eine Möglichkeit, es zu hacken, aber Sie hacken immer noch die JVM-Funktion, um Ihren Code für Sie neu zu ordnen und zu optimieren.

* Ich kann nicht überprüfen, ob der zuerst hinzugefügte Puffer immer zuerst gedruckt wird, aber höchstwahrscheinlich.

27
Gemtastic

Wenn Sie die Eclipse-Konsole verwenden, scheint es zwei verschiedene Phänomene zu geben:
Wie durch @Gemtastic beschrieben, handelt es sich um die JVMs, die die Streams handhaben, und die andere ist die Art, wie Eclipse diese Streams liest, wie von @DraganBozanovic erwähnt. Da ich Eclipse verwende, ist die von @BillK veröffentlichte elegante flush()-Lösung, die nur die JVM-Ausgabe betrifft, nicht ausreichend.

Am Ende schrieb ich mir eine Hilfsklasse namens EclipseTools mit folgendem Inhalt (und der erforderlichen Paketdeklaration und -importen). Es ist ein bisschen ein Hacker, behebt aber beide Probleme:

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)));
    }
}

Zur Verwendung rufen Sie einfach EclipseTools.fixConsole() am Anfang Ihres Codes auf.

Im Wesentlichen werden dadurch die beiden Streams System.err und System.out durch einen benutzerdefinierten Satz von Streams ersetzt, die ihre Daten einfach an die ursprünglichen Streams weiterleiten, aber verfolgen, für welchen Stream zuletzt geschrieben wurde. Wenn sich der Stream, in den geschrieben wird, ändert, z. B. System.err.something(...), gefolgt von System.out.something(...), wird die Ausgabe des letzten Streams geleert und auf 200 ms gewartet, damit die Eclipse-Konsole genug Zeit hat, um den Druckvorgang abzuschließen.

Hinweis: Die 200ms sind nur ein grober Anfangswert. Wenn dieser Code reduziert, das Problem jedoch nicht behoben wird, erhöhen Sie die Verzögerung in Thread.sleep von 200 auf etwas höher, bis es funktioniert. Wenn diese Verzögerung zwar funktioniert, sich jedoch auf die Leistung Ihres Codes auswirkt (wenn Sie häufig Streams wechseln), können Sie alternativ versuchen, den Code schrittweise zu reduzieren, bis Sie Fehler erhalten.

9
Markus A.

Die beiden println-Anweisungen werden von zwei verschiedenen Threads verarbeitet. Die Ausgabe hängt wiederum davon ab, in welcher Umgebung Sie den Code ausführen. Zum Beispiel habe ich den folgenden Code in IntelliJ und in der Befehlszeile jeweils fünfmal ausgeführt. 

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

Daraus ergibt sich folgende Ausgabe:
Befehlszeile 

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 

Ich denke, verschiedene Umgebungen behandeln die Puffer unterschiedlich.
Eine Möglichkeit, um zu sehen, dass diese Streams von verschiedenen Threads tatsächlich gehandhabt werden, ist das Hinzufügen einer sleep-Anweisung in die Schleife. Sie können versuchen, den Wert, den Sie für den Ruhezustand festlegen, zu ändern und zu sehen, dass diese von verschiedenen Threads tatsächlich verarbeitet werden.

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();
            }
        }
    }
}

Die Ausgabe erwies sich in diesem Fall als

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 

Eine Möglichkeit, sie zum Drucken in der gleichen Reihenfolge zu zwingen, wäre die Funktion .flush(), die für mich funktioniert hat. Aber es scheint, dass nicht jeder die richtigen Ergebnisse erzielt.

Die zwei Streams, die von zwei verschiedenen Threads gehandhabt werden, sind wahrscheinlich der Grund, warum manchmal die ERROR-Nachricht von einigen Bibliotheken gedruckt wird, die wir verwenden, und vor einigen Print-Anweisungen gedruckt werden, die wir in der Reihenfolge der Ausführung sehen sollten.

2
Rakesh

Dies ist ein Fehler in Eclipse . Es scheint, dass Eclipse separate Threads verwendet, um den Inhalt von out- und err-Streams ohne Synchronisation zu lesen.

Wenn Sie die Klasse kompilieren und in der Konsole ausführen (mit dem klassischen Java <main class name>), ist die Reihenfolge wie erwartet.

1

Ich habe Thread verwendet, um die Ausgabe von System.out und System.err sequentiell zu drucken:

    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());
        }
    }
0
Milan Paudyal