Чтение ** небуферизованного ** потока вывода другого процесса

Я программирую небольшой графический интерфейс для конвертера файлов в java. Конвертер файлов записывает свой текущий прогресс в stdout. Выглядит так:

Flow_1.wav: 28% complete, ratio=0,447

Я хотел проиллюстрировать это на индикаторе выполнения, поэтому читаю стандартный вывод процесса следующим образом:

ProcessBuilder builder = new ProcessBuilder("...");
builder.redirectErrorStream(true);
Process proc = builder.start();
InputStream stream = proc.getInputStream();
byte[] b = new byte[32];
int length;
while (true) {
    length = stream.read(b);
    if (length < 0) break;
    // processing data
}

Теперь проблема в том, что независимо от того, какой размер массива байтов я выберу, поток читается кусками по 4 КБ. Итак, мой код выполняется до length = stream.read(b);, а затем на некоторое время блокируется. Как только процесс генерирует выходные данные размером 4 КБ, моя программа получает этот фрагмент и обрабатывает его 32-байтовыми фрагментами. А потом снова ждет следующих 4 КБ.

Я попытался заставить Java использовать буферы меньшего размера, например:

BufferedInputStream stream = new BufferedInputStream(proc.getInputStream(), 32);

Или это:

BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()), 32);

Но ничего не изменил.

Затем я нашел это: Источник процесса (около строки 87)

Кажется, что класс Process реализован таким образом, что он передает стандартный вывод процесса в файл. Итак, что на самом деле делает proc.getInputStream();, это возвращает поток в файл. И этот файл вроде бы записан с буфером 4 КБ.

Кто-нибудь знает какое-то обходное решение для этой ситуации? Я просто хочу мгновенно получить результат процесса.

РЕДАКТИРОВАТЬ: как было предложено Яном Робертсом, я также попытался направить вывод преобразователя в поток stderr, поскольку этот поток, похоже, не заключен в BufferedInputStream. Еще 4к кусков.

Еще одна интересная вещь: на самом деле я получаю не ровно 4096 байт, а примерно на 5 больше. Боюсь, что сама FileInputStream буферизирована изначально.


person R2-D2    schedule 19.02.2013    source источник
comment
не то, чтобы это меняло вашу проблему, но java не передает данные в файл. файловые дескрипторы - это то, как операционные системы описывают потоки ввода / вывода процесса. они не обязательно соотносятся с реальными файлами (хотя могут).   -  person jtahlborn    schedule 19.02.2013
comment
Обычно stdout уже буферизирован процессом, который его записывает, поэтому в этом случае у вас нет шансов, если вы не можете изменить другой процесс. Этому процессу нужно будет чаще отключать буферизацию или вызывать flush ().   -  person Philipp Wendler    schedule 20.02.2013
comment
@PhilippWendler: stdout конвертера окончательно очищается чаще, чем каждые 4 КБ (при запуске с терминала). Вы думаете, что автоматический сброс в \ n отключается, когда stdout не отправляется на консоль?   -  person R2-D2    schedule 20.02.2013
comment
@ R2-D2 Хотя это технически возможно, я бы не ожидал, что приложение сделает это. Я знаю, что ОС также может выполнять буферизацию, но я не знаю подробностей об этом. Может быть, разобраться в этом направлении?   -  person Philipp Wendler    schedule 20.02.2013
comment
Вы можете написать небольшую программу на C, которая запускает ваш процесс и считывает ввод, чтобы увидеть, является ли это проблемой, специфичной для Java, или нет.   -  person Philipp Wendler    schedule 20.02.2013
comment
Я не могу найти, как захватить потоки stdout других процессов в c. Но я нашел рубиновое решение с использованием псевдотерминалов. (ссылка) К сожалению, нет эквивалент java (afaik).   -  person R2-D2    schedule 21.02.2013
comment
Ой, я тупой. Конвертер должен регулярно сбрасывать свой стандартный вывод, потому что прогресс всегда выводится в одну и ту же строку. Он использует \ r, не \ n. \ r не сбрасывается автоматически, поэтому он должен вызывать fflush. Для меня это не имеет смысла!   -  person R2-D2    schedule 21.02.2013


Ответы (1)


Если посмотреть на код, который вы связали со стандартным потоком вывода процесса, он оборачивается BufferedInputStream, но его стандартная ошибка остается небуферизованной. Таким образом, одна из возможностей может заключаться в запуске не конвертера напрямую, а сценария оболочки (или эквивалента Windows, если вы работаете в Windows), который отправляет stdout конвертера в stderr:

ProcessBuilder builder = new ProcessBuilder("/bin/sh", "-c",
  "exec /path/to/converter args 1>&2");

Не надо redirectErrorStream, а затем читать из proc.getErrorStream() вместо proc.getInputStream().

Возможно, ваш конвертер уже использует stderr для отчетов о ходе выполнения, и в этом случае вам не нужен бит скрипта, просто выключите redirectErrorStream(). Если программа конвертера записывает и в stdout, и в stderr, вам нужно будет создать второй поток, который также будет использовать stdout (подход сценария позволяет обойти это, отправляя все в stderr).

person Ian Roberts    schedule 19.02.2013
comment
Это не сработало. Еще 4к кусков. В любом случае спасибо за ваш подход ниндзя. ;) - person R2-D2; 19.02.2013