Java: синхронизация стандартного выхода и стандартной ошибки - PullRequest
9 голосов
/ 25 мая 2011

У меня странная проблема, и было бы неплохо, если бы я мог ее решить.В целях отладки (и некоторых других вещей) я пишу журнал консольного Java-приложения на стандартный вывод.Некоторые вещи, которые написаны по стандарту out, и некоторые вещи, как ошибки, печатаются по стандартной ошибке.Проблема в том, что эти два не синхронизированы идеально, поэтому порядок напечатанных строк не всегда правильный.Я предполагаю, что это потому, что многие вещи печатаются, и бывает, что буфер для одного выхода заполнен, поэтому другой вывод печатается до того, как первый очистит его.

Например, я хочу написать это:

syso: aaa
syso: bbb
syso: ccc
syso: ddd
syso: eee
syserr: ---

Иногда печатается

aaa
bbb
ccc
---
ddd
eee

Иногда между ними нет новой строки, поэтому она выглядит как

aaa
bbb
ccc---

ddd
eee

Каждый раз, когда я что-то печатаю навывод, я сбрасываю тот же вывод с

System.out.flush();

или

System.err.flush();

Как решить эту проблему?Кстати, все напечатано в консоли Eclipse.

Ответы [ 6 ]

9 голосов
/ 25 мая 2011

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

Тем не менее, есть (очень плохое, но строгое) решение вашей проблемы:

System.out.println(...);
System.out.flush();
Thread.sleep(100);

System.err.println(...);
System.err.flush();
Thread.sleep(100);

Возможно, вам придется изменить продолжительность сна в зависимости от вашей конфигурации!

2 голосов
/ 17 мая 2014

Я знаю, что этот пост является древним, но он все еще является проблемой сегодня, поэтому здесь есть другое решение, которое решает проблему, используя ответ @ EserAygün, но таким образом, что вам не нужно искать и изменять каждое место в вашем проекте, где пишем System.out или System.err.

Создайте себе класс с именем EclipseTools со следующим содержимым (а также требуемым объявлением пакета и импортом):

public class EclipseTools {

    private static OutputStream lastStream = null;
    private static boolean      isFixed    = false;

    private static class FixedStream extends OutputStream {

        private final OutputStream target;

        public FixedStream(OutputStream originalStream) {
            target = originalStream;
        }

        @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 (isFixed) return;
        isFixed = true;
        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 до чего-то большего, пока он не заработает. В качестве альтернативы, если эта задержка работает, но влияет на производительность вашего кода (если вы часто чередуете потоки), вы можете постепенно уменьшать ее, пока не начнете получать ошибки.

2 голосов
/ 01 декабря 2012

Это давняя ошибка Eclipse: https://bugs.eclipse.org/bugs/show_bug.cgi?id=32205.

1 голос
/ 25 мая 2011

java.lang.System.setErr(java.lang.System.out);

заставляет приложение использовать стандартный вывод в качестве потока ошибок.

0 голосов
/ 14 сентября 2015

Проблема заключается в использовании консоли Eclipse. Обычно std out записывает байты по одному на консоль, и std err тоже, но красным. Однако метод не ожидает записи всех байтов перед возвратом. Итак, что я рекомендую это:

import java.io.OutputStream;
import java.io.PrintStream;
import java.util.function.IntConsumer;

public final class Printer extends PrintStream {
    public static final Printer out = new Printer(
            e -> System.out.print((char) e));
    public static final Printer err = new Printer(
            e -> System.err.print((char) e));
    private final IntConsumer printer;
    private static final Object lock = "";

    private Printer(IntConsumer printer) {
        super(new OutputStream() {
            public void write(int b) {
                printer.accept(b);
            }
        });
        this.printer = printer;
    }

    public void print(int x) {
        synchronized (lock) {
            this.print(Integer.toString(x));
        }
    }

    public void print(boolean x) {
        synchronized (lock) {
            this.print(Boolean.toString(x));
        }
    }

    public void print(double x) {
        synchronized (lock) {
            this.print(Double.toString(x));
        }
    }

    public void print(float x) {
        synchronized (lock) {
            this.print(Float.toString(x));
        }
    }

    public void print(long x) {
        synchronized (lock) {
            this.print(Long.toString(x));
        }
    }

    public void print(char x) {
        synchronized (lock) {
            this.print(Character.toString(x));
        }
    }

    public void print(char[] x) {
        synchronized (lock) {
            StringBuffer str = new StringBuffer(x.length);
            for (char c : x) {
                str.append(c);
            }
            this.print(str);
        }
    }

    public void print(Object x) {
        synchronized (lock) {
            this.print(x.toString());
        }
    }

    public void print(String x) {
        synchronized (lock) {
            x.chars().forEach(printer);
        }
    }

    public void println(int x) {
        synchronized (lock) {
            this.print(Integer.toString(x) + "\n");
        }
    }

    public void println(boolean x) {
        synchronized (lock) {
            this.print(Boolean.toString(x) + "\n");
        }
    }

    public void println(double x) {
        synchronized (lock) {
            this.print(Double.toString(x) + "\n");
        }
    }

    public void println(float x) {
        synchronized (lock) {
            this.print(Float.toString(x) + "\n");
        }
    }

    public void println(long x) {
        this.print(Long.toString(x) + "\n");
    }

    public void println(char x) {
        synchronized (lock) {
            this.print(Character.toString(x) + "\n");
        }
    }

    public void println(char[] x) {
        synchronized (lock) {
            StringBuffer str = new StringBuffer(x.length);
            for (char c : x) {
                str.append(c);
            }
            this.print(str + "\n");
        }
    }

    public void println(Object x) {
        synchronized (lock) {
            this.print(x.toString() + "\n");
        }
    }

    public void println(String x) {
        synchronized (lock) {
            x.chars().forEach(printer);
            printer.accept('\n');
        }
    }
}

Используйте Printer.out и Printer.err вместо System.out и System.err. У него все те же ошибки, но это работает намного лучше.

0 голосов
/ 25 мая 2011
public class Util

    synchronized public static void printToOut(...)
        out.print(...)

    synchronized public static void printToErr(...)
        err.print(...)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...