Байтовые потоки
Когда данные — просто байты
Изображение, архив, PDF и сетевой пакет нельзя корректно описать как набор символов.
На самом нижнем привычном уровне Java работает с такими данными через байтовые потоки:
InputStream читает байты, а OutputStream записывает их.
Конкретный источник или приёмник задаёт подкласс. Например,
FileInputStream читает байты из файла,
а FileOutputStream пишет их в файл.
Код выше по цепочке может при этом работать с общим типом потока и не зависеть от источника.
try (InputStream in = new FileInputStream("input.bin")) {
int first = in.read();
System.out.println(first);
}
Метод read() возвращает int, а не byte:
значения 0..255 представляют прочитанный байт,
а -1 означает конец потока.
Так один результат может различать все возможные значения байта и специальный признак EOF.
Чтение блоками
Читать большой файл по одному байту неудобно и обычно неэффективно. Поэтому данные часто читают сразу в массив байтов.
byte[] buffer = new byte[4096];
try (InputStream in = new FileInputStream("input.bin")) {
int read;
while ((read = in.read(buffer)) != -1) {
System.out.println("Read: " + read);
}
}
Переменная read показывает, сколько элементов массива действительно заполнено на этой итерации.
Последний блок легко может быть короче размера буфера, поэтому нельзя автоматически считать весь массив новыми данными.
Запись байтов
OutputStream устроен симметрично: он принимает отдельные байты или массивы байтов.
Если нужно дописать данные в конец существующего файла, это поведение задаётся при создании конкретного файлового потока.
byte[] data = {10, 20, 30};
try (OutputStream out = new FileOutputStream("output.bin")) {
out.write(data);
}