Process.getInputStream: примеры (JAVA)

Process.getInputStream в Java: разбор и примеры
Раздел: Работа с процессами и памятью (Runtime, Process)
Process.getInputStream: InputStream

Общее описание метода Process.getInputStream()

Метод Process.getInputStream() класса java.lang.Process предоставляет поток для чтения стандартного вывода запущенного процесса (stdout дочернего процесса). Метод не принимает аргументов и возвращает объект java.io.InputStream. Полученный поток позволяет получить байтовый вывод процесса в текущем JVM-процессе.

Когда применяется: при необходимости выполнения внешней программы и обработки её вывода из Java-кода, например при парсинге результатов утилит системы, запуске консольных инструментов или организации конвейеров между процессами.

Аргументы и возвращаемое значение

Аргументы: отсутствуют. Метод всегда вызывается как process.getInputStream(), где process - объект типа Process.

Возвращаемое значение: java.io.InputStream. Поток может быть пустым, блокирующим (ожидающим данных) или закрытым, в зависимости от состояния дочернего процесса и его стандартного вывода.

Поведение и особенности

  • Поток представляет stdout дочернего процесса, а не stdin. Для записи в stdin дочернего процесса применяется Process.getOutputStream().
  • Метод сам по себе не бросает проверяемых исключений, но операции чтения из возвращенного InputStream могут бросать IOException.
  • Если stdout дочернего процесса производит большой объем данных, отсутствие чтения из потока может привести к заполнению буфера операционной системы и блокировке дочернего процесса.
  • Кодировка символов не задаётся в самом InputStream. Для получения текста необходимо оборачивать поток в InputStreamReader с указанием кодировки либо применять BufferedReader для чтения строк.
  • Слияние stdout и stderr выполняется опцией ProcessBuilder.redirectErrorStream(true), после чего getInputStream() будет содержать объединенный вывод.
  • В Java 9+ доступна удобная операция InputStream.transferTo(OutputStream), упрощающая копирование данных из getInputStream() в целевой поток.

Простые примеры чтения stdout процесса

Примеры показывают разные способы чтения вывода: чтение строк, чтение байтов, использование transferTo. В примерах показаны команды для Unix и Windows по возможности. Результаты представлены в блоке результата.

// Пример 1. Чтение строк (Unix)
import java.io.*;

public class Example1 {
    public static void main(String[] args) throws Exception {
        Process p = new ProcessBuilder("echo", "Hello from process").start();
        try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())) ) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println("OUT: " + line);
            }
        }
        p.waitFor();
    }
}
OUT: Hello from process
// Пример 2. Чтение байтов
import java.io.*;

public class Example2 {
    public static void main(String[] args) throws Exception {
        Process p = new ProcessBuilder("echo", "byte output").start();
        InputStream in = p.getInputStream();
        int b;
        while ((b = in.read()) != -1) {
            System.out.print((char) b);
        }
        p.waitFor();
    }
}
byte output
// Пример 3. transferTo (Java 9+)
import java.io.*;

public class Example3 {
    public static void main(String[] args) throws Exception {
        Process p = new ProcessBuilder("echo", "transferTo example").start();
        try (InputStream in = p.getInputStream(); OutputStream out = System.out) {
            in.transferTo(out);
        }
        p.waitFor();
    }
}
transferTo example

Связанные методы в Java

  • Process.getErrorStream() - поток для чтения стандартного потока ошибок (stderr). Предпочтителен при необходимости отдельной обработки ошибок.
  • Process.getOutputStream() - поток для записи в стандартный ввод дочернего процесса (stdin). Используется для передачи данных в запущенную программу.
  • ProcessBuilder.redirectOutput() и redirectErrorStream(true) - позволяют перенаправить вывод в файл или объединить stderr и stdout. Удобны при желании избежать явного чтения потоков из Java кода.
  • ProcessHandle и расширенная Process API (Java 9+) - дают дополнительную информацию о процессе, но не заменяют getInputStream для чтения stdout.

Выбор зависит от задачи: для простого чтения stdout применяется getInputStream; для объединенного вывода подходит redirectErrorStream; для записи в процесс используется getOutputStream; для логирования в файл предпочтительны redirectOutput или передача вывода на файловую систему.

Аналоги в других языках

Краткое сравнение с примерами поведения и отличий.

  • Python
    import subprocess
    p = subprocess.Popen(["echo", "hello"], stdout=subprocess.PIPE)
    out = p.stdout.read()
    print(out.decode().strip())
    hello

    Отличие: Python предоставляет stdout как файловый объект сразу при создании Popen. Кодировка должна указываться при декодировании.

  • Node.js (JavaScript)
    const { spawn } = require('child_process');
    const p = spawn('echo', ['hello']);
    p.stdout.on('data', (data) => { console.log(data.toString().trim()); });
    hello

    Отличие: поток stdout в Node.js событийно ориентирован, подходит для неблокирующего чтения.

  • C#
    using System.Diagnostics;
    var p = new Process();
    p.StartInfo.FileName = "cmd"; p.StartInfo.Arguments = "/c echo hello";
    p.StartInfo.RedirectStandardOutput = true; p.Start();
    string out = p.StandardOutput.ReadToEnd();
    Console.WriteLine(out.Trim());
    hello

    Отличие: .NET предоставляет обертки StreamReader для StandardOutput и опции перенаправления через StartInfo.

  • Go
    package main
    import (
        "fmt"
        "os/exec"
    )
    func main() {
        cmd := exec.Command("echo", "hello")
        out, _ := cmd.Output()
        fmt.Print(string(out))
    }
    
    hello
    

    Отличие: Go возвращает собранный вывод удобным методом Output или предоставляет StdoutPipe для стриминга.

  • PHP
    $proc = popen('echo hello', 'r');
    $out = stream_get_contents($proc);
    pclose($proc);
    echo trim($out);
    
    hello
  • Kotlin
    val p = ProcessBuilder("echo", "hello").start()
    val out = p.inputStream.bufferedReader().readText()
    println(out.trim())
    hello

    Отличие: Kotlin использует тот же JVM API, но предоставляет удобные расширения для работы с потоками.

  • Lua
    local f = io.popen('echo hello')
    local out = f:read('*a')
    f:close()
    print(out:match('%S+'))
    hello
  • SQL

    SQL не выполняет внешние процессы напрямую. В СУБД иногда доступны расширения или процедуры для вызова ОС, но они специфичны для платформы и не имеют прямого аналога getInputStream.

Типичные ошибки и их проявления

  • Неправильное понимание: ожидание в getInputStream вывода stdin. Последствие: поиск данных в неправильном потоке.
  • Не чтение stderr: если программа пишет ошибки в stderr, getInputStream() не покажет их без redirectErrorStream(true). Пример: программа пишет в stderr, а код читает только stdout - вывод отсутствует.
  • Блокировка из-за незачитанных потоков: дочерний процесс может зависнуть при заполнении системного буфера. Часто проявляется как вечное ожидание p.waitFor().
  • Неправильная кодировка: чтение байтов без указания кодировки приводит к искажению символов при не-UTF8 выводе.

Пример ошибки: зависание из-за отсутствия чтения stderr и stdout одновременно.

// Ошибочный пример: может зависнуть при большом выводе
Process p = new ProcessBuilder("someCommandThatPrintsALot").start();
try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
    // читается только stdout
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}
p.waitFor();
Программа может зависнуть из-за переполнения stderr буфера, если процесс пишет и туда

Пример ошибки с кодировкой

// Чтение без указания кодировки приводит к возможному искажению
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
String s = br.readLine();
System.out.println(s);
Если вывод в CP1251, результат в консоли UTF-8 будет искажен

Рекомендации по устранению: одновременное чтение stderr и stdout (в отдельных потоках) или объединение потоков, указание кодировки при создании InputStreamReader, использование try-with-resources для закрытия потоков.

Изменения и эволюция API

Сам метод Process.getInputStream() присутствует в Java с ранних версий и концептуально не менялся. Основные улучшения вокруг интерфейса Process появились в следующих версиях:

  • Java 5: ввод ProcessBuilder для более гибкого создания процессов.
  • Java 7: улучшения по управлению вводом/выводом и try-with-resources, упрощение закрытия потоков.
  • Java 9: добавлен метод InputStream.transferTo(OutputStream), который упрощает передачу данных из getInputStream; введен ProcessHandle и дополнения к управлению процессом.

Изменений в сигнатуре getInputStream не происходило, но появление вспомогательных методов и API делает работу с потоком более удобной в новых версиях Java.

Расширенные и нетипичные примеры использования

Несколько продвинутых сценариев: параллельное чтение stdout и stderr, потоковая передача вывода в файл, чтение бинарных данных и парсинг прогресс-вывода.

Пример java
// Пример A. Параллельное чтение stdout и stderr, чтобы избежать блокировок
import java.io.*;
import java.util.concurrent.*;

public class ParallelStreams {
    public static void main(String[] args) throws Exception {
        Process p = new ProcessBuilder("bash", "-c", "echo out; echo err 1>&2; sleep 1; echo out2").start();

        ExecutorService ex = Executors.newFixedThreadPool(2);
        Callable readStream(InputStream in) {
            return () -> {
                try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
                    StringBuilder sb = new StringBuilder();
                    String s;
                    while ((s = br.readLine()) != null) {
                        sb.append(s).append('\n');
                    }
                    return sb.toString();
                }
            };
        }

        Future stdout = ex.submit(readStream(p.getInputStream()));
        Future stderr = ex.submit(readStream(p.getErrorStream()));

        int code = p.waitFor();
        System.out.println("exit=" + code);
        System.out.println("STDOUT:\n" + stdout.get());
        System.out.println("STDERR:\n" + stderr.get());
        ex.shutdown();
    }
}
exit=0
STDOUT:
out
out2

STDERR:
err
Пример java
// Пример B. Стриминг вывода процесса в файл с использованием transferTo (Java 9+)
import java.io.*;

public class StreamToFile {
    public static void main(String[] args) throws Exception {
        Process p = new ProcessBuilder("yes", "line").start(); // генерирует много строк
        try (InputStream in = p.getInputStream();
             OutputStream fos = new FileOutputStream("out.txt")) {
            // ограничение: читается первые N байт при помощи промежуточного буфера
            byte[] buf = new byte[8192];
            int read, total = 0, limit = 100_000; // например 100 KB
            while ((read = in.read(buf)) != -1 && total < limit) {
                int toWrite = Math.min(read, limit - total);
                fos.write(buf, 0, toWrite);
                total += toWrite;
            }
        }
        p.destroyForcibly();
    }
}
Результат: файл out.txt с частью вывода команды yes
Пример java
// Пример C. Чтение бинарного вывода и поиск сигнатуры
import java.io.*;

public class BinaryRead {
    public static void main(String[] args) throws Exception {
        Process p = new ProcessBuilder("xxd", "-p", "/bin/ls").start();
        try (InputStream in = p.getInputStream()) {
            byte[] buffer = in.readAllBytes(); // Java 9+
            // поиск подпоследовательности, например первые 4 байта
            for (int i = 0; i < Math.min(16, buffer.length); i++) {
                System.out.printf("%02x ", buffer[i]);
            }
            System.out.println();
        }
        p.waitFor();
    }
}
Пример вывода: шестнадцатеричные байты начала файла
Пример java
// Пример D. Выполнение команды с вводом и чтением результата (двунаправленное общение)
import java.io.*;

public class Bidirectional {
    public static void main(String[] args) throws Exception {
        Process p = new ProcessBuilder("python3", "-u", "-c", "print(input())").start();
        try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(p.getOutputStream()));
             BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
            bw.write("Hello Python\n");
            bw.flush();
            String reply = br.readLine();
            System.out.println("Reply: " + reply);
        }
        p.waitFor();
    }
}
Reply: Hello Python

Пояснения: в сложных сценариях рекомендуется использовать отдельные потоки для чтения stdout и stderr, задавать кодировку явно, применять временные файлы или transferTo при больших объёмах и аккуратно завершать процессы через destroy/destroyForcibly при необходимости.

джава Process.getInputStream function comments

En
Process.getInputStream Gets the input stream of the subprocess