Потоки ввода-вывода в Java (InputStream, OutputStream). Практические Задания
Попробуйте записать в файл букву 'я' через FileOutputStream и прочитать её обратно — вместо неё вы получите латинскую O, и никакого исключения не будет:
try (OutputStream out = new FileOutputStream("a.txt")) {
out.write('я'); // write(int) берёт только младший байт: 0x044F -> 0x4F
}
try (InputStream in = new FileInputStream("a.txt")) {
System.out.println((char) in.read()); // Вывод: O
} Задания по потокам ввода-вывода в Java закрепляют модуль «Java I/O»: вы перепишете копирование файлов на try-with-resources, скопируете текстовый файл построчно через BufferedReader и BufferedWriter, рекурсивно обойдёте каталог с помощью класса File и запишете отчёты в файл через PrintWriter в двух сквозных проектах — «Отчёт о сотрудниках» и «Интернет-магазин».
Что повторить перед решением
Для задач этой страницы нужны три навыка: работать с байтовыми и символьными потоками, автоматически закрывать их конструкцией try-with-resources и получать список файлов каталога через класс File. Если что-то из этого вызывает сомнения, сначала откройте соответствующий урок модуля.
| Урок | Что понадобится в задачах |
|---|---|
| Типы байтовых потоков | FileInputStream и FileOutputStream, чтение до -1 |
| Конструкция try-with-resources | Автоматическое закрытие одного и нескольких потоков вместо finally |
| Классы символьных потоков | BufferedReader, BufferedWriter, FileReader, FileWriter, PrintWriter |
| Использование класса File | listFiles(), isDirectory() для рекурсивного обхода каталога |
| Использование потоков данных | Общая схема: открыть поток, читать или писать, закрыть |
Класс FileCopy
Перепишите класс FileCopy, который копирует файл побайтно:
package io;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) throws IOException {
FileInputStream fileIn = null;
FileOutputStream fileOut = null;
try {
fileIn = new FileInputStream("src/io/file.txt");
fileOut = new FileOutputStream("src/io/copied_file.txt");
int a;
while ((a = fileIn.read()) != -1) {
fileOut.write(a);
}
} finally {
if (fileIn != null) {
fileIn.close();
}
if (fileOut != null) {
fileOut.close();
}
}
}
} - Замените ручное закрытие потоков в блоке
finallyна try-with-resources. - Добавьте блок
catchдля обработкиIOExceptionи уберитеthrows IOExceptionиз сигнатуры методаmain.
Подсказка
Оба потока объявите в одних скобках через точку с запятой: try (FileInputStream fileIn = new FileInputStream(...); FileOutputStream fileOut = new FileOutputStream(...)). Они закроются автоматически в обратном порядке, даже если при копировании возникнет исключение, поэтому проверки на null и блок finally больше не нужны.
Класс FileInputOutputStreamDemo
Перепишите класс FileInputOutputStreamDemo:
package io;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
public class FileInputOutputStreamDemo {
public static void main(String[] args) {
try {
char[] symbols = {'a', 'b', 'c'};
OutputStream output = new FileOutputStream("a.txt");
for (int i = 0; i < symbols.length; i++) {
// Запись каждого символа в текстовый файл
output.write(symbols[i]);
}
output.close();
InputStream input = new FileInputStream("a.txt");
int size = input.available();
for (int i = 0; i < size; i++) {
// Чтение текстового файла посимвольно
System.out.print((char) input.read() + " ");
}
input.close();
} catch (IOException e) {
System.out.print("Exception");
}
}
} - Перепишите код с использованием try-with-resources для
OutputStreamиInputStream. - Замените цикл записи
for (int i = 0; i < symbols.length; i++)на циклfor-each. - Записывайте файл
a.txtв каталогsrc/io: путь к файлу —"src/io/a.txt".
Ожидаемый вывод программы: a b c
Неочевидный момент
Удобнее записать и прочитать файл в двух последовательных блоках try-with-resources: тогда поток записи гарантированно закрыт до начала чтения. Цикл чтения на for-each не переводится, а available() не обязан возвращать полный объём данных — надёжнее читать до конца потока: while ((b = input.read()) != -1).
Копирование файла в другой файл
- Напишите класс, который построчно копирует содержимое одного текстового файла в другой.
- Для чтения используйте
BufferedReaderповерхFileReader, для записи —BufferedWriterповерхFileWriter. - Закрывайте потоки с помощью try-with-resources.
Нюанс с readLine()
readLine() возвращает строку без символа перевода строки, а в конце файла — null. После каждой записанной строки вызывайте newLine(), иначе весь текст склеится в одну строку. Чтобы кириллица не исказилась, укажите кодировку явно: new FileReader(file, StandardCharsets.UTF_8) (Java 11+).
Запись отчёта о сотрудниках в файл
Отчёт о сотрудниках · часть 4 из 4 · ← часть 3
- Добавьте в класс
Employeeметод, который записывает отчёт о сотрудниках в файл. - Используйте класс
PrintWriter.
С чего начать
У PrintWriter есть те же методы print(), println() и printf(), что и у System.out: если отчёт уже выводится на консоль, достаточно направить вывод в файл. Методы записи PrintWriter не выбрасывают IOException — после записи проверьте checkError().
Рекурсивный вывод содержимого каталога
- Напишите метод, который рекурсивно обходит заданный каталог и выводит информацию обо всех файлах и каталогах, находящихся в нём и во всех его подкаталогах.
- Используйте класс
Fileи рекурсию.
Подсказка
Передавайте в метод File и уровень вложенности: для каждого элемента listFiles(), который является каталогом, вызывайте тот же метод с уровнем на единицу больше, а отступ в выводе стройте по уровню. listFiles() возвращает null, если путь не является каталогом или к нему нет доступа, — без проверки получите NullPointerException.
Запись чека в файл через PrintWriter
Интернет-магазин · часть 8 из 13 · ← часть 7 · часть 9 →
Часть 1, часть 2, часть 3, часть 4, часть 5, часть 6, часть 7.
- После оформления покупки запишите в файл отчёт о содержимом корзины.
- Для записи и форматирования отчёта используйте класс
PrintWriter.
Как оформить отчёт
Откройте PrintWriter в try-with-resources, чтобы содержимое буфера гарантированно попало в файл. Каждую позицию корзины выводите через printf() с фиксированной шириной колонок, например "%-20s %5d %10.2f%n", а в конце — итоговую сумму.
На чём чаще всего ошибаются
Чаще всего решения по вводу-выводу ломаются молча: программа отрабатывает без исключений, но файл оказывается пустым, обрезанным или с искажённой кириллицей. Причина почти всегда в неверно выбранном потоке, незакрытом буфере или кодировке по умолчанию. Ниже — типичные промахи и способы их исправить.
| Ошибка | Что произойдёт | Как исправить |
|---|---|---|
outputStream.write('я') | Записывается только младший байт, при чтении получается O | Для текста используйте Writer с явной кодировкой: new FileWriter(file, StandardCharsets.UTF_8) |
Размер файла через input.available() | Метод не обязан возвращать весь объём данных; для сетевых потоков часто возвращает 0 | Читайте до конца потока: while ((b = in.read()) != -1) |
BufferedWriter или PrintWriter без close() | Данные остаются в буфере, файл пустой или обрезан | Открывайте поток в try-with-resources |
Закрытие нескольких потоков подряд в finally | Если первый close() выбросит IOException, второй поток не закроется | Объявите оба потока в одном try-with-resources |
Нет проверки результата PrintWriter | Ошибка записи (например, переполнен диск) не выбрасывает IOException — отчёт теряется молча | После записи проверьте checkError() |
for (File f : dir.listFiles()) без проверки | NullPointerException, если путь не каталог или к нему нет доступа | Проверьте результат listFiles() на null |
new FileWriter("report.txt") без кодировки | До Java 17 включительно используется кодировка платформы (в русской Windows — windows-1251), с Java 18 (JEP 400) — UTF-8; файл открывается по-разному на разных машинах | Указывайте StandardCharsets.UTF_8 явно (конструкторы с Charset есть с Java 11) |
Проверь свой код
Перед отправкой решения сверьтесь с рекомендациями по оформлению кода: переменные потоков называйте по смыслу (reader, writer, а не a и b), не оставляйте пустых блоков catch и не объявляйте throws IOException у main, если по условию исключение нужно обработать.
Часто задаваемые вопросы
Какие потоки выбрать для копирования файла: байтовые или символьные?
Байтовые (InputStream/OutputStream) копируют любой файл — картинку, архив, PDF — без изменений. Символьные (Reader/Writer) нужны, когда файл обрабатывается как текст: построчно, с учётом кодировки. Двоичный файл через Reader копировать нельзя — байты будут испорчены декодированием. Подробнее — в уроках Типы байтовых потоков и Классы символьных потоков.
Нужен ли блок finally, если используется try-with-resources?
Для закрытия потоков — нет: ресурсы закрываются автоматически в порядке, обратном объявлению, даже если в блоке try возникло исключение. Если при этом close() тоже выбросит исключение, оно не потеряется, а будет добавлено к основному и доступно через e.getSuppressed(). finally оставляют только для действий, не связанных с закрытием ресурсов.
Как скопировать файл в современной Java без ручного цикла?
Одной строкой: Files.copy(Path.of("src/io/file.txt"), Path.of("src/io/copy.txt"), StandardCopyOption.REPLACE_EXISTING). Files.copy появился в Java 7, Path.of — в Java 11. В задачах страницы цикл пишется вручную намеренно: так видно, как работают read(), буферы и закрытие потоков.
Почему программа не находит файл или создаёт его не там, где ожидалось?
Относительный путь вроде "src/io/a.txt" отсчитывается от рабочего каталога процесса, а не от папки с классом. Узнать его можно через System.getProperty("user.dir"); в IntelliJ IDEA по умолчанию это корень проекта. Если каталога src/io нет, FileOutputStream выбросит FileNotFoundException — сам каталог он не создаёт.
Комментарии