InputStream.read: примеры (JAVA)

InputStream.read: разбор методов и сценариев
Раздел: Потоки ввода-вывода (Streams) байтовые
InputStream.read: int

Общее описание InputStream.read

Метод InputStream.read в Java используется для чтения байтов из потока ввода. Он представлен в нескольких перегруженных вариантах: int read(), int read(byte[] b) и int read(byte[] b, int off, int len). Поведение у всех вариантов общее: при достижении конца потока возвращается -1, при успешном чтении возвращается число фактически прочитанных байтов или байт (в случае read() возвращается значение байта в виде 0..255).

Подробно по вариантам:

  • int read(): читает один байт и возвращает его как целое 0..255. Если поток закончился, возвращает -1. Может блокировать выполнение до появления байта или конца потока. Выбрасывает IOException при ошибке ввода-вывода.
  • int read(byte[] b): пытается заполнить весь массив b. Возвращает количество прочитанных байтов (меньше длины массива возможно при достижении EOF или если чтение вернуло меньше данных). При EOF сразу возвращает -1. Может блокировать. Выбрасывает IOException.
  • int read(byte[] b, int off, int len): читает до len байтов и кладет их в массив b, начиная с индекса off. Возвращает число прочитанных байтов или -1 при EOF. Требует проверки границ аргументов: b не должен быть null, off и len неотрицательны, off + len <= b.length. На неверные аргументы выбрасывается IndexOutOfBoundsException или NullPointerException. Также может выбросить IOException.

Дополнительно, начиная с Java 9, в InputStream появились вспомогательные методы: readAllBytes(), readNBytes(int), readNBytes(byte[] b, int off, int len) и transferTo(OutputStream), упрощающие чтение всего содержимого или фиксированного числа байтов.

Особенности поведения:

  • Методы могут блокировать, если источник данных блокирующий (например, сокет или System.in).
  • Количества байтов, возвращаемые вариантом с массивом, могут быть меньше запрошенных, даже если не достигнут EOF. Нужно обрабатывать частичные чтения в цикле при необходимости получить точно N байтов.
  • Метод available() не гарантирует общее количество оставшихся байтов, он лишь оценивает количество немедленно доступных байтов.

Простые примеры использования

Ниже приведены короткие примеры основных вариантов чтения с показом кода и результата.

Чтение одного байта из ByteArrayInputStream

import java.io.*;

byte[] data = {65, 66};
try (InputStream in = new ByteArrayInputStream(data)) {
    int b1 = in.read();
    int b2 = in.read();
    int b3 = in.read();
    System.out.println(b1);
    System.out.println(b2);
    System.out.println(b3);
}
65
66
-1

Чтение в буфер

import java.io.*;

byte[] data = "Hello".getBytes();
try (InputStream in = new ByteArrayInputStream(data)) {
    byte[] buf = new byte[3];
    int n1 = in.read(buf); // может быть 3
    int n2 = in.read(buf); // оставшиеся 2
    System.out.println(n1);
    System.out.println(n2);
    System.out.println(new String(buf, 0, n2));
}
3
2
lo

Чтение с указанием offset и len

import java.io.*;

byte[] data = {1,2,3,4,5};
try (InputStream in = new ByteArrayInputStream(data)) {
    byte[] buf = new byte[5];
    int read = in.read(buf, 1, 3); // записать в buf[1..3]
    System.out.println(read);
    for (int i = 0; i < buf.length; i++) System.out.print(buf[i] + " ");
}
3
0 1 2 3 0 

Похожие методы внутри Java

Внутри Java есть несколько альтернатив и надстроек над InputStream.read с отличиями по удобству и производительности:

  • BufferedInputStream: оборачивает источник и уменьшает число системных вызовов, предпочтительнее при множественных мелких чтениях.
  • DataInputStream: предоставляет методы для чтения примитивных типов (readInt, readUTF), удобен при чтении бинарных форматов.
  • Files.readAllBytes(Path): читает весь файл в память, удобно для небольших файлов, не подходит для очень больших данных.
  • ReadableByteChannel / FileChannel: NIO API, даёт неблокирующие и позиционные операции, предпочтение при высокопроизводительном вводе-выводе и работе с большими файлами.
  • Reader (например InputStreamReader): для чтения текстовых данных с учётом кодировки; использовать вместо байтовых методов при работе с символами.

Выбор зависит от задачи: для побайтного чтения использовать BufferedInputStream, для парсинга бинарных форматов DataInputStream, для чтения всего контента Files.readAllBytes, для производительных и неблокирующих решений NIO-каналы.

Аналоги в других языках и отличия

Короткие примеры и отличия от Java.

  • Python (io, open): f.read(n) возвращает bytes или строку. Есть readinto(buffer) для чтения в существующий буфер. Отличие: Python возвращает пустую bytes при EOF, Java возвращает -1. Пример:
# Python
with open('file.bin','rb') as f:
    b = f.read(3)
    print(b)
    b2 = f.read(3)
    print(b2)
b'Hel'
b'lo'
  • Node.js (JavaScript): поток fs.createReadStream, чтение через события 'data' или метод read на stream. Отличие: событийная модель и буферы Buffer. Пример:
// Node.js
const fs = require('fs');
const rs = fs.createReadStream('file.txt', { highWaterMark: 3 });
rs.on('data', chunk => console.log(chunk.toString()));
Hel
lo
  • PHP: fopen/fread или file_get_contents. fread возвращает строку или false при ошибке. Пример:
<?
$h = fopen('file.txt','rb');
echo fread($h, 3);
echo fread($h, 3);
?>
Hel
lo
  • C# (.NET): Stream.Read(byte[] buffer, int offset, int count) возвращает количество прочитанных байтов или 0 при EOF. Отличие: EOF возвращает 0, а не -1. Пример:
using(var fs = File.OpenRead("file.txt")){
  byte[] buf = new byte[3];
  int n = fs.Read(buf,0,3);
  Console.WriteLine(n);
}
3
  • Go: интерфейс io.Reader с методом Read(p []byte) (n int, err error), при EOF возвращает n=0 и err=io.EOF. Отличие: явный err для EOF. Пример:
// Go
r := bytes.NewReader([]byte("Hello"))
b := make([]byte,3)
n, err := r.Read(b)
fmt.Println(n, string(b[:n]), err)
3 Hel <nil>
  • Kotlin: использует Java InputStream, добавлены extension функции readBytes() и readNBytes(). Синтаксис и поведение аналогичны Java.
  • Lua: io.read(n) читает n символов или nil при ошибке/EOF.
  • SQL: прямых аналогов нет, работу с бинарными данными выполняют драйверы DB (BLOB API), чтение через потоки драйвера.

Главное отличие большинства языков в представлении EOF: Java использует возвращаемое -1 для байтовых чтений и отдельные вспомогательные методы появились только в поздних версиях.

Типичные ошибки при использовании

Наиболее распространенные ошибки и их проявления.

  • Непроверка значения возврата на -1. Результат: использование -1 как байта, некорректные данные.
InputStream in = new ByteArrayInputStream(new byte[]{1});
byte[] b = new byte[2];
int n = in.read(b);
System.out.println(b[1]); // неинициализированное значение, если n==1
0 (возможна некорректная логика)
  • Ожидание, что метод с массивом всегда заполнит весь массив. Результат: частичное чтение и неверная обработка данных. Решение: читать в цикле до получения нужного объема или до EOF.
byte[] buf = new byte[1024];
int read = in.read(buf);
// если требуется полностью 1024 байта, необходимо повторить чтение в цикле
  • Неправильное использование available() для определения EOF. available() может вернуть 0, хотя данных ещё есть; полагаться на него для чтения не рекомендуется.
  • Не закрытие потоков, особенно при работе с файлами и сетевыми ресурсами. Приводит к утечкам дескрипторов. Использование try-with-resources решает проблему.
  • Неправильная обработка кодировок при преобразовании байтов в строки. Следует использовать явную кодировку: new String(bytes, StandardCharsets.UTF_8).
  • Чтение больших файлов в память с помощью readAllBytes() или накопление в ByteArrayOutputStream без лимитов ведет к OutOfMemoryError.

Изменения и дополнения в последних версиях Java

В Java 9 и выше были добавлены удобные методы в класс InputStream:

  • readAllBytes(): читает весь поток в новый массив байтов. Удобно для небольших источников, потенциально опасно для больших данных.
  • readNBytes(int) и readNBytes(byte[] b, int off, int len): читают заданное число байтов или до EOF и не блокируют сверх требуемого объема.
  • transferTo(OutputStream): копирует оставшиеся данные в указанный OutputStream, оптимизировано для внутренних реализаций и часто быстрее ручного цикла с буфером.

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

Расширенные и редкие сценарии использования

Несколько подробных примеров с пояснениями и результатами.

Чтение точно N байтов с циклом (без Java 9)

Пример java
import java.io.*;

byte[] source = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes();
try (InputStream in = new ByteArrayInputStream(source)) {
    int toRead = 10;
    byte[] out = new byte[toRead];
    int read = 0;
    while (read < toRead) {
        int r = in.read(out, read, toRead - read);
        if (r == -1) break; // EOF
        read += r;
    }
    System.out.println(read);
    System.out.println(new String(out, 0, read));
}
10
ABCDEFGHIJ

Использование readAllBytes() и предостережение

Пример java
import java.io.*;

try (InputStream in = new FileInputStream("bigfile.bin")) {
    byte[] all = in.readAllBytes();
    System.out.println(all.length);
} catch (OutOfMemoryError e) {
    System.out.println("Файл слишком большой");
}
(выведет размер массива или сообщение о нехватке памяти)

Копирование потока в другой поток с transferTo

Пример java
import java.io.*;

byte[] data = "Hello transfer".getBytes();
try (InputStream in = new ByteArrayInputStream(data);
     OutputStream out = new ByteArrayOutputStream()) {
    in.transferTo(out);
    System.out.println(out.toString());
}
Hello transfer

Чтение из сокета с таймаутом и обработка прерываний

Пример java
import java.io.*;
import java.net.*;

Socket s = new Socket();
s.connect(new InetSocketAddress("example.com", 80), 2000);
s.setSoTimeout(3000);
try (InputStream in = s.getInputStream();
     OutputStream out = s.getOutputStream()) {
    out.write("GET / HTTP/1.0\r\n\r\n".getBytes());
    byte[] buf = new byte[4096];
    int n;
    while ((n = in.read(buf)) != -1) {
        System.out.print(new String(buf, 0, n));
    }
} catch (SocketTimeoutException e) {
    System.out.println("Таймаут чтения");
}
(вывод HTTP-ответа или сообщение о таймауте)

Параллельное чтение с ограничением памяти: чтение чанками и сохранение в файл

Пример java
import java.io.*;

try (InputStream in = new FileInputStream("large.bin");
     OutputStream out = new FileOutputStream("copy.bin")) {
    byte[] buf = new byte[64*1024];
    int n;
    while ((n = in.read(buf)) != -1) {
        out.write(buf, 0, n);
    }
}
(копирование файла по чанкам, экономия памяти)

Чтение в заранее выделенный ByteBuffer через InputStream -> Channel

Пример java
import java.io.*;
import java.nio.*;
import java.nio.channels.*;

try (ReadableByteChannel rbc = Channels.newChannel(new FileInputStream("file.bin"))) {
    ByteBuffer bb = ByteBuffer.allocate(1024);
    while (rbc.read(bb) != -1) {
        bb.flip();
        // обработка данных
        bb.clear();
    }
}
(побайтовая обработка с NIO)

джава InputStream.read function comments

En
InputStream.read Reads the next byte of data from the input stream