Вывод System.out.print на консоль виден сразу. То есть PrintStream сбрасывает после каждой печати, а не только println?

Из документации PrintStream:

При желании можно создать PrintStream для автоматической очистки; это означает, что метод flush вызывается автоматически после записи байтового массива, запускается один из методов println или пишется символ новой строки или байт ('\n').

Тогда дан код

System.out.print("hi");   // gives console output: hi
System.out.print(7);      // gives console output: 7

// prevents flushing when stream wiil be closed at app shutdown
for (;;) {
}

Почему тогда я вижу вывод на мою консоль? Ничто не должно быть записано в консоль (экземпляр PrintStream из System.out), потому что пока ничего не должно быть сброшено!

Это не ответило на это.

Я предполагаю, что ответ находится в исходном коде (метод частной утилиты BufferedWriter.flushBuffer()), но я не понимаю комментарий к коду: "Сбрасывает выходной буфер в базовый символьный поток, не сбрасывая сам поток ": если PrintStream(который привязан к выводу консоли), то есть "сам поток", не очищается, вывод на консоль не обновляется!...

Источник для PrintStream.print(String):

private void write(String s) {
        try {
            synchronized (this) {
                ensureOpen();
                textOut.write(s);
                textOut.flushBuffer();
                charOut.flushBuffer();
                if (autoFlush && (s.indexOf('\n') >= 0))
                    out.flush();
            }
        }
        catch (InterruptedIOException x) {
            Thread.currentThread().interrupt();
        }
        catch (IOException x) {
            trouble = true;
        }
    }

Источник для BufferedWriter.flushBuffer():

/**
     * Flushes the output buffer to the underlying character stream, without
     * flushing the stream itself.  This method is non-private only so that it
     * may be invoked by PrintStream.
     */
    void flushBuffer() throws IOException {
        synchronized (lock) {
            ensureOpen();
            if (nextChar == 0)
                return;
            out.write(cb, 0, nextChar);
            nextChar = 0;
        }
    }

Более подробная информация также приводится здесь. Это очень сложно, но на каком-то этапе BufferedWriter передается конструктору PrintStream.

1 ответ

Я пошел шаг за шагом, используя отладчик, и вот что я нашел: введите описание изображения здесьString s отображается в консоли после 527-й строки, поэтому перед строкой 528, в которой проверяется наличие \n готово.

В charOut.flushBuffer() глубоко внутри существует следующий метод: введите описание изображения здесь

В котором, проверка о \n пропал, отсутствует.

Поток выглядит следующим образом:

  1. System.out#print(String s) звонки PrintStream#print(String s),
  2. PrintStream#print(String s) звонки PrintStream#write(String s),
  3. PrintStream#write(String s) звонки OutputSteamWriter#flushBuffer(),
  4. OutputStreamWriter#flushBuffer() звонки StreamEncoder#flushBuffer(),
  5. StreamEncoder#flushBuffer() звонки StreamEncoder#implFlushBuffer(),
  6. StreamEncoder#implFlushBuffer() звонки StreamEncoder#writeBytes(),
  7. StreamEncoder#writeBytes() звонки PrintStream#write(byte buf[], int off, int len) который сбрасывает буфор if(autoFlush),

Самые важные фрагменты выше. BufferedWriter кажется, не вызывать в этом потоке.

https://bugs.openjdk.java.net/browse/JDK-8025883 описывает эту ошибку.

Этот бит меня в программе, которая читает и анализирует двоичный файл, делая много System.out.printf() звонки, которые заняли больше времени, чем следовало.

В итоге я написал вспомогательный класс, который нарушает контракт Streams, не выполняя каждый запрос сброса:

class ForceBufferedOutputStream extends OutputStream {

    OutputStream out;
    byte[] buffer;
    int buflen;
    boolean haveNewline;
    private static final int bufsize=16384;

    public ForceBufferedOutputStream(OutputStream out) {
        this.out=out;
        this.buffer=new byte[bufsize];
        this.buflen=0;
        this.haveNewline=false;
    }
    @Override
    public void flush() throws IOException {
        if (this.haveNewline || this.buflen==bufsize) {
            out.write(buffer, 0, buflen);
            out.flush();
            this.buflen=0;
            this.haveNewline=false;
        }
    }
    @Override
    public void close() throws IOException {
        out.close();
    }

    @Override
    public void write(int b) throws IOException {
        buffer[buflen++]=(byte)b;
        if (b=='\n')
            this.haveNewline=true;
        if (buflen==bufsize)
            this.flush();
    }
}

затем с помощью new PrintStream(new ForceBufferedOutputStream(System.out)) вместо System.out,

Я считаю, что это ужасная часть программного обеспечения - как сказано, это нарушает контракт, который flush() необходимо убедиться, что все написано, и это может оптимизировать массив write звонки. Но в моем случае время выполнения было сокращено с 17 минут до 3:45, поэтому, если вам нужна функция копирования / вставки, которая ускоряет быстрые и грязные программы, я надеюсь, что это поможет.

Другие вопросы по тегам