Подтвердить что ты не робот

Java: System.out.println и System.err.println не работают

Мои вызовы 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. Почему это?

4b9b3361

Ответ 1

Они разные потоки и размываются в разное время.

Если вы положили

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

внутри вашего цикла, он будет работать как ожидалось.

Чтобы уточнить, выходные потоки кэшируются, поэтому вся запись поступает в этот буфер памяти. После периода тишины они фактически списываются.

Вы пишете два буфера, затем после периода бездействия они оба размываются (один за другим).

Ответ 2

Это вызвано особенностью 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();

Поскольку буферизация многих буферов только для их очистки сразу после этого занимает гораздо больше времени, чем для буферизации всего буфера, а затем сбрасывает все это одновременно.

Как его решить

Это может привести к разработке кода и архитектуры; вы вроде бы не решаете этого. Чтобы обойти это, вы должны сделать его более эффективным для буферизации печати/флеша, печати буфера или флеша, чем буфер, а затем сброс. Это, скорее всего, соблазнит вас неудачным дизайном. Если для вас важно, как вывести его упорядоченно, я предлагаю вам попробовать другой подход. Для-looping с .flush() является одним из способов взломать его, но вы все еще взламываете JVM-функцию, чтобы повторно упорядочить и оптимизировать свой код для вас.

* Я не могу проверить, что буфер, который вы добавили в первую очередь, всегда будет печатать первым, но, скорее всего, будет.

Ответ 3

Если вы используете консоль 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)));
    }
}

Чтобы использовать, просто вызовите EclipseTools.fixConsole() один раз в начале вашего кода.

В основном, это заменяет два потока System.err и System.out на пользовательский набор потоков, которые просто пересылают свои данные в исходные потоки, но отслеживают, какой поток был написан для последнего. Если поток, который записывается для изменений, например a System.err.something(...), за которым следует a System.out.something(...), он очищает вывод последнего потока и ждет 200 мс, чтобы дать время консоли Eclipse завершить его печать.

Примечание. 200 мс - это просто приблизительное начальное значение. Если этот код уменьшает, но не устраняет проблему для вас, увеличьте задержку в Thread.sleep от 200 до уровня выше, пока он не будет работать. В качестве альтернативы, если эта задержка работает, но влияет на производительность вашего кода (если вы чередуете потоки часто), вы можете попытаться уменьшить его постепенно, пока не начнете получать ошибки.

Ответ 4

Это ошибка в Eclipse. Кажется, что Eclipse использует отдельные потоки для чтения содержимого потоков out и err без какой-либо синхронизации.

Если вы скомпилируете класс и выполните его в консоли (с классическим java <main class name>), порядок будет таким, как ожидалось.

Ответ 5

Два оператора 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 

Я думаю, что разные среды обрабатывают буферы по-разному.
Одним из способов увидеть, что эти потоки являются infact, обрабатываемые разными потоками, является добавление оператора 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, напечатанное некоторыми библиотеками, которые мы используем, печатая перед некоторыми заявлениями печати, которые мы должны были видеть в соответствии с порядком выполнение.

Ответ 6

Я использовал поток для вывода вывода 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());
        }
    }

Ответ 7

В частности, в Eclipse теперь у вас есть Eclipse 2019-09 синхронизированный стандарт и вывод ошибок в консоли.

Представление Eclipse Console в настоящее время не может гарантировать, что смешанный стандарт и вывод ошибок отображаются в том же порядке, в котором они были запущены запущенным процессом.

Для приложений Java в конфигурации запуска Common Tab теперь есть возможность объединить стандартный вывод и вывод ошибок.
Это гарантирует, что стандартный вывод и вывод ошибок отображаются в том же порядке, в котором они были созданы, но в то же время отключает отдельную раскраску вывода ошибок.

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