Типы байтовых потоков в Java: InputStream и OutputStream - Вопросы

Всего: 6 вопросов

1. 

Что возвращает InputStream.read(byte[] buffer) и почему при копировании файла нужно писать out.write(buffer, 0, count), а не out.write(buffer)?

Метод read(byte[] buffer) читает до buffer.length байт и возвращает число реально прочитанных байт, а в конце потока — -1. Прочитать меньше, чем размер массива, он может в любой момент, а последний блок файла почти всегда заполнен не полностью.

Если записать весь массив через out.write(buffer), в копию попадут «хвосты» предыдущего блока, и файл станет больше оригинала. Поэтому пишут только прочитанное:

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

С Java 9 весь цикл заменяет одна строка in.transferTo(out).

2. 

Можно ли использовать InputStream.available(), чтобы узнать размер файла или прочитать поток до конца?

Нет. available() возвращает лишь оценку количества байт, которые можно прочитать прямо сейчас без блокировки. Это не размер файла и не остаток потока: для сетевого потока метод часто возвращает 0, хотя данные ещё придут.

Цикл вида for (int i = 0; i < in.available(); i++) может работать с локальным файлом, но на сокете ничего не прочитает. Правильный способ — читать, пока read() не вернёт -1, или использовать readAllBytes() / transferTo().

Исключение — ByteArrayInputStream: данные лежат в памяти, поэтому его available() возвращает точное число оставшихся байт.

3. 

Какие методы для чтения и копирования потока целиком появились в InputStream и OutputStream в Java 9 и Java 11?

Java 9:

  • byte[] readAllBytes() — читает все оставшиеся байты; для очень больших потоков возможен OutOfMemoryError.
  • long transferTo(OutputStream out) — копирует остаток потока в выходной поток и возвращает число скопированных байт.

Java 11:

  • byte[] readNBytes(int len) — читает до len байт; в конце потока возвращает пустой массив, а не -1.
  • InputStream.nullInputStream() — пустой поток, сразу возвращающий -1.
  • OutputStream.nullOutputStream() — поток, который молча отбрасывает все записанные байты.

Строку из потока получают так: new String(in.readAllBytes(), StandardCharsets.UTF_8) — кодировку лучше указывать явно.

4. 

Какие конструкторы есть у FileInputStream и FileOutputStream и когда они выбрасывают FileNotFoundException?

Оба класса принимают имя файла строкой (String) или объект File. У FileOutputStream есть ещё вариант со вторым параметром boolean append:

InputStream in = new FileInputStream("data.bin");
OutputStream out = new FileOutputStream("log.txt");        // файл обнуляется
OutputStream app = new FileOutputStream("log.txt", true);  // дописывание в конец

FileNotFoundException (наследник IOException) выбрасывается:

  • у FileInputStream — если файла нет, это каталог или его нельзя открыть для чтения;
  • у FileOutputStream — если файл нельзя создать или открыть: например, не существует каталог, в котором он должен лежать, или путь указывает на каталог. Отсутствующий файл при этом просто создаётся.

Важно: конструктор без append обнуляет существующий файл ещё до первой записи.

5. 

Чем ByteArrayInputStream и ByteArrayOutputStream отличаются от файловых потоков и нужно ли их закрывать?

Они работают с массивом в памяти: ByteArrayInputStream читает из переданного массива, а ByteArrayOutputStream пишет в автоматически растущий массив, который возвращает toByteArray(). Их удобно подставлять туда, где метод ждёт InputStream/OutputStream, например в юнит-тестах.

  • read(), write(int) и write(byte[], int, int) переопределены без throws IOException — в памяти ошибке ввода-вывода взяться неоткуда.
  • writeBytes(byte[]) (Java 11) записывает весь массив без IOException, в отличие от унаследованного write(byte[]).
  • toString(Charset) (Java 10) превращает накопленные байты в строку в заданной кодировке.
  • available() у ByteArrayInputStream возвращает точный остаток байт.

Закрывать их технически не нужно: close() у обоих классов не имеет эффекта, и после него методы продолжают работать без IOException. Обязательно закрывать потоки, связанные с ресурсами ОС: файлами, сокетами и обёртками над ними.

6. 

Зачем нужны BufferedInputStream и BufferedOutputStream и какую роль при этом играет flush()?

Это декораторы (наследники FilterInputStream/FilterOutputStream) над другим байтовым потоком. Они читают и пишут данные блоками через внутренний буфер — по умолчанию 8 КБ (8192 байта), размер можно задать вторым параметром конструктора: new BufferedInputStream(in, 64 * 1024). Вместо системного вызова на каждый read() или write(int) поток обращается к файлу один раз на блок, поэтому побайтовая обработка ускоряется в разы.

flush() выталкивает накопленные в буфере байты в приёмник. Если не вызвать flush() и не закрыть BufferedOutputStream, последние байты останутся в памяти и не попадут в файл. close() сам вызывает flush(), а закрытие внешней обёртки закрывает и вложенный FileOutputStream. У самого FileOutputStream метод flush() ничего не делает.

Страница 1 из 1