Потоки данных (stream) в Java: ввод-вывод и упаковка потоков - Вопросы

Всего: 5 вопросов

1. 

Что такое поток данных (stream) в Java и на какие виды делятся потоки ввода-вывода?

Поток данных — упорядоченная последовательность байтов или символов, которая передаётся между программой и источником или приёмником данных: файлом, консолью, сетевым соединением, массивом в памяти. Программа читает и пишет через одни и те же методы read() и write(), не зная, какое устройство на другом конце. Классы потоков находятся в пакете java.io.

Потоки делятся по двум признакам:

  • по типу данных — байтовые (8-битные байты, подходят для любых данных: картинок, архивов) и символьные (символы char, UTF-16, для текста);
  • по направлению — потоки ввода (input, чтение) и вывода (output, запись).

Сочетание признаков даёт четыре базовых абстрактных класса: InputStream и OutputStream (байтовые), Reader и Writer (символьные). Вид класса видно по суффиксу: ...Stream — байтовый, ...Reader/...Writer — символьный. В файле всегда лежат байты: символьный поток — это байтовый поток плюс кодировка.

2. 

Чем поток ввода-вывода (InputStream) отличается от потока Stream API и от потока выполнения Thread?

По-русски все три называют «потоком», но это разные, не связанные между собой вещи:

  • Поток ввода-вывода (InputStream, OutputStream, Reader, Writer из java.io) — последовательность байтов или символов между программой и файлом, консолью, сетью. Пример: new FileInputStream("photo.jpg").
  • Stream API (java.util.stream.Stream, Java 8) — конвейер операций над элементами коллекции или массива; сам по себе ничего с диска не читает. Пример: list.stream().filter(...).count().
  • Поток выполнения (java.lang.Thread) — отдельная ветка выполнения кода, работающая параллельно с другими.

FileInputStream не наследует Stream и не запускает Thread. Единственная точка соприкосновения — метод BufferedReader.lines(), который превращает строки из I/O-потока в Stream<String>.

3. 

Из каких шагов состоит общая схема работы с любым потоком ввода-вывода в Java?

Три шага:

  1. Создать потоковый объект и связать его с источником или приёмником данных (файлом, сокетом, массивом).
  2. Пока есть данные, читать их из потока или записывать в поток. Байтовый read() возвращает -1, когда данные закончились.
  3. Закрыть поток — чтобы освободить ресурс операционной системы и сбросить буферы.

В современном коде третий шаг выполняет try-with-resources (Java 7+): поток закрывается сам, даже если внутри возникло исключение.

try (InputStream in = new FileInputStream("data.bin")) { // 1. открыть
    int b;
    while ((b = in.read()) != -1) {                       // 2. читать, пока не -1
        // обработать байт b
    }
}                                                         // 3. close() вызовется сам
4. 

Что такое упаковка (wrapping) потоков в Java и как она связана с паттерном «декоратор»?

Упаковка — приём, при котором один поток передаётся в конструктор другого и получает от него новые возможности: буферизацию, чтение строк, форматированный вывод, перевод байтов в символы. Внешний поток (обёртка) делегирует реальное чтение и запись внутреннему.

PrintWriter printer = new PrintWriter(new BufferedWriter(new FileWriter("log.txt")));

Здесь FileWriter пишет в файл, BufferedWriter копит символы в буфере, PrintWriter добавляет println() и printf().

Это паттерн «декоратор»: обёртка имеет тот же тип, что и оборачиваемый поток (BufferedReader — тоже Reader), поэтому обёртки вкладываются друг в друга в любом порядке без отдельного класса под каждую комбинацию. В java.io это видно по базовым классам FilterInputStream, FilterOutputStream, FilterReader, FilterWriter: они хранят ссылку на вложенный поток и по умолчанию передают ему все вызовы.

5. 

Как прочитать текстовый файл построчно в нужной кодировке через цепочку обёрток и какую обёртку в цепочке нужно закрывать?

Байтовый поток оборачивают в InputStreamReader (мост от байтов к символам с заданной кодировкой), а его — в BufferedReader (буфер и метод readLine(), который возвращает null в конце потока):

try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(
                new FileInputStream("notes.txt"), StandardCharsets.UTF_8))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

Кодировку лучше указывать явно: без Charset берётся кодировка по умолчанию, которая до Java 17 включительно зависела от ОС (с Java 18 — UTF-8).

Закрывать нужно только самую внешнюю обёртку: её close() сначала сбрасывает свой буфер, затем закрывает вложенный поток, и так до файла. Если закрыть внутренний поток раньше внешнего, данные из буфера обёртки теряются, а close() обёртки бросит IOException: Stream closed.

Страница 1 из 1