JavaRush /Курсы /JAVA 25 SELF /Чтение и запись бинарных файлов: InputStream, OutputStrea...

Чтение и запись бинарных файлов: InputStream, OutputStream

JAVA 25 SELF
36 уровень , 2 лекция
Открыта

1. Введение

Давайте начнём с самого простого: чем бинарный файл отличается от текстового? Текстовый файл — это такой файл, который можно открыть в обычном блокноте и увидеть буквы, цифры, пробелы и прочие символы. Например, my_notes.txt или poem.txt.

Бинарный файл — это файл, который содержит не текст, а произвольные байты. Это может быть картинка (.jpg, .png), музыка (.mp3), архив (.zip), исполняемый файл (.exe), видео (.mp4), файл базы данных и так далее. Если вы откроете такой файл в блокноте, увидите нечто вроде ÿØÿà или целую стену непонятных символов. Это нормально! Компьютер «понимает» только байты — для него и текст, и картинка, и видео — просто последовательность байтов. В текстовых файлах эти байты можно интерпретировать как символы, а в бинарных — это «сырые» данные, которые не предназначены для чтения человеком.

Основные классы для работы с бинарными файлами

В Java для работы с бинарными файлами используются потоки байтов:

  • InputStream — базовый класс для чтения байтов.
  • OutputStream — базовый класс для записи байтов.

Для работы с файлами есть их конкретные реализации:

  • FileInputStream — читает байты из файла.
  • FileOutputStream — пишет байты в файл.

Если вы слышали про FileReader и FileWriter, то знайте: они работают с символами и подходят только для текста. Для бинарных файлов используйте только InputStream/OutputStream и их наследников.

2. Чтение бинарных файлов

Чтение по одному байту

Самый простой способ — читать файл по одному байту. Это наглядно, но очень медленно.

try (FileInputStream in = new FileInputStream("image.jpg")) {
    int b;
    while ((b = in.read()) != -1) {
        // b — это число от 0 до 255 (байт), -1 означает конец файла
        // Можно обработать байт, например, посчитать сумму всех байтов
    }
}

Метод read() возвращает следующий байт как int (от 0 до 255), а когда файл закончился — возвращает -1. Обычно по одному байту читают только если нужно что-то очень специфичное (например, анализировать структуру файла).

Чтение блоками (с буфером)

Читать по одному байту — это как ходить в магазин за каждым яблоком отдельно. Намного эффективнее брать сразу целый пакет! В Java для этого есть метод read(byte[] buffer), который заполняет массив байтами из файла.

try (FileInputStream in = new FileInputStream("image.jpg")) {
    byte[] buffer = new byte[4096]; // буфер на 4 КБ
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        // buffer содержит bytesRead байт из файла
        // Можно обработать эти байты, например, сохранить их куда-то ещё
    }
}

Метод read(buffer) возвращает, сколько байт реально прочитано (может быть меньше размера буфера, особенно в последнем чтении). Такой способ гораздо быстрее, потому что обращений к диску меньше.

Пример: копирование файла

Напишем простую программу, которая копирует любой бинарный файл (например, картинку) из одного места в другое.

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BinaryCopyExample {
    public static void main(String[] args) {
        String source = "cat.jpg";
        String dest = "cat_copy.jpg";

        try (FileInputStream in = new FileInputStream(source);
             FileOutputStream out = new FileOutputStream(dest)) {

            byte[] buffer = new byte[8192]; // 8 КБ — оптимальный размер для большинства задач
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
            System.out.println("Копирование завершено!");
        } catch (IOException e) {
            System.out.println("Ошибка при копировании: " + e.getMessage());
        }
    }
}

Всё просто: читаем блоки из исходного файла и тут же пишем их в новый файл. Такой способ работает с любыми файлами: картинками, архивами, видео.

3. Запись бинарных файлов

Запись массива байтов

Если у вас есть массив байтов (например, вы получили его из сети или сгенерировали в программе), вы можете записать его в файл вот так:

byte[] data = new byte[] {1, 2, 3, 4, 5}; // пример массива

try (FileOutputStream out = new FileOutputStream("data.bin")) {
    out.write(data); // записывает весь массив в файл
}

Метод write(byte[]) записывает все байты из массива. Также можно записать только часть массива: out.write(data, offset, length).

Запись файла по частям (например, при копировании)

Как и при чтении, обычно используют буфер:

try (FileInputStream in = new FileInputStream("source.bin");
     FileOutputStream out = new FileOutputStream("dest.bin")) {

    byte[] buffer = new byte[4096];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
}

Здесь всё, что мы читаем из файла, сразу пишем в другой файл. Такой код часто встречается в программах-архиваторах, загрузчиках, обработчиках изображений и т.д.

4. Полезные нюансы

Почему нельзя использовать Reader/Writer для бинарных файлов?

Reader и Writer работают с символами (char), а не с байтами. Они автоматически преобразуют байты в символы согласно кодировке (например, UTF-8). Это удобно для текста, но для бинарных файлов — смертельно опасно!

Если вы попробуете записать картинку через FileWriter, получите испорченный файл, который невозможно открыть. Запомните: для любых нетекстовых файлов используйте только InputStream/OutputStream!

Важные отличия и нюансы работы с бинарными файлами

  • Размер буфера: Слишком маленький буфер замедлит работу (слишком много обращений к диску), слишком большой — займёт лишнюю память. 416 КБ — обычно оптимально.
  • Обработка ошибок: Всегда обрабатывайте IOException — файл может не существовать, быть заблокирован или закончиться место на диске.
  • Закрытие потоков: Используйте try-with-resources — это гарантирует закрытие файлов даже при ошибках.
  • Перезапись файла: Если вы открываете файл через new FileOutputStream("file.bin"), он будет перезаписан. Чтобы дописать в конец, используйте конструктор с параметром append = true.
  • Права доступа: Если программа не может открыть файл, проверьте права на чтение/запись.
  • readAllBytes(): Позволяет прочитать весь файл в массив байтов одной строкой. Для больших файлов — не используйте, чтобы не «съесть» всю память!

5. Типичные ошибки при работе с бинарными файлами

Ошибка №1: Использование FileReader/FileWriter для бинарных файлов. Это приведёт к порче данных, потому что эти классы преобразуют байты в символы и обратно, что для картинок, архивов и т.п. — катастрофа.

Ошибка №2: Игнорирование возвращаемого значения read(). Метод read(byte[]) может прочитать меньше байтов, чем вы просите, особенно в последнем блоке. Всегда используйте возвращаемое значение, чтобы знать, сколько байт реально обработано.

Ошибка №3: Забыли закрыть поток. Если не закрыть файл, он может остаться заблокированным, а данные не будут записаны до конца (особенно при записи!). Используйте try-with-resources.

Ошибка №4: Попытка читать файл целиком в память без учёта размера. Для больших файлов это приведёт к OutOfMemoryError. Используйте буфер и читайте по частям.

Ошибка №5: Не обрабатываются исключения. Работа с файлами всегда может привести к ошибкам: файл не найден, нет прав, диск переполнен. Не забывайте обрабатывать IOException.

1
Задача
JAVA 25 SELF, 36 уровень, 2 лекция
Недоступна
Извлечение ценного фрагмента из бинарного потока 🧪
Извлечение ценного фрагмента из бинарного потока 🧪
1
Задача
JAVA 25 SELF, 36 уровень, 2 лекция
Недоступна
Подсчёт энергетического потенциала артефакта ⚛️
Подсчёт энергетического потенциала артефакта ⚛️
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ