Потоки данных (stream) в Java: ввод-вывод и упаковка потоков - Вопросы
Всего: 5 вопросов
1. Что такое поток данных (stream) в Java и на какие виды делятся потоки ввода-вывода?
Что такое поток данных (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) отличается от потока 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?
Из каких шагов состоит общая схема работы с любым потоком ввода-вывода в Java?
Три шага:
- Создать потоковый объект и связать его с источником или приёмником данных (файлом, сокетом, массивом).
- Пока есть данные, читать их из потока или записывать в поток. Байтовый
read()возвращает-1, когда данные закончились. - Закрыть поток — чтобы освободить ресурс операционной системы и сбросить буферы.
В современном коде третий шаг выполняет 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 и как она связана с паттерном «декоратор»?
Что такое упаковка (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.