Чтение данных из потока в Java

Я достаточно часто сталкиваюсь с проблемами, вызваными неправильным чтением данных из потока (java.io.InputStream). В последнее время такие проблемы почему-то стали появляться особенно часто, в связи с чем я решил разъяснить принцип раз и навсегда и просто давать всем желающим ссылку.

Чаще всего чтение производят с помощью метода int read(). Этот вариант, безусловно, имеет право на существование, если знать, как дальше быть с возвращаемым результатом (который int, а вовсе не byte). Мне лично ближе чтение блоками. Оно как-то понятнее и избавляет от всех вопросов преобразования значения.

Итак, код:

code: #java
// входной поток, получается откуда-то извне
InputStream is;
// буфер для чтения, разумного объема
byte[] buffer = new byte[32768];
// Выходной поток, ByteArrayOutputStream используется только для примера
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// цикл чтения
while (true){
    // читаем данные в буфер
    int readBytesCount = is.read(buffer);
    if (readBytesCount == -1) {
        // данные закончились
        break;
    }
    if (readBytesCount > 0) {
        // данные были считаны - есть, что записать
        baos.write(buffer, 0, readBytesCount);
    }
}
baos.flush();
baos.close();
byte[] data = baos.toByteArray();

Разбираем, что к чему.

На входе у нас есть поток. Безразлично, откуда он взялся. Мы из него читаем. Что делается дальше:

  1. Выделяется буфер разумного размера. В этом примере - 32Кб, в принципе, размер зависит только от объема читаемых данных (если надо прочитать 100 байт, выделять 1Мб будет неразумно)
  2. Дальше в примере создается ByteArrayOutputStream. Это просто пример выходного потока, если у вас есть, куда писать данные - этот шаг необязателен. :)
  3. Дальше начинается цикл чтения. Он бесконечный, выход реализован внутри. В цикле мы делаем следующее:
    • Читаем данные в буфер с помощью метода is.read(byte[] buffer). В этом методе будет прочитано от 0 до buffer.length байт. Это надо запомнить на всю жизнь - никто не гарантирует вычитывания всего буфера, даже если данных достаточно! Реальное количество прочитанных байтов возвращается из метода как значение.
    • Если реальное количество прочитанных байтов равно -1, это означает, что данные закончились. На это можно полагаться. В этом случае мы прерываем выполнение цикла чтения.
    • Если прочитано больше нуля байтов (а может быть и ноль!) - мы записываем данные с помощью метода класса OutputStream write(byte[] buffer, int position, int length). Обратите внимание - поскольку буфер может быть заполнен при последнем чтении не полностью, необходимо указывать, сколько байтов из него надо взять.
    • Продолжаем выполнение цикла.
  4. Сбрасываем остаток данных в поток и закрываем его
  5. Получаем данные в виде массива байтов

Как видите, ничего сложного. Разве что стоит добавить обработку исключений, которую я опустил, чтобы код не загромождать.

Еще я видел вариант, основанный на методе класса InputStream - int available(). Он возвращает количество байтов, доступных для чтения без блокировки. Это в теории. На практике - он реализован не во всех потоках, а реализация по умолчанию возвращает 0. Потому лично я его никогда не использую.

Описанный метод является универсальным при чтении блоками. В частности, он работает при чтении из символьных потоков (java.io.Reader), вместо типа byte при этом используется char.

P.P.S. Напоследок хочу напомнить. Читать из потока byte, преобразовывать его в char простым приведением типа и пытаться построить из полученных "символов" строку - грубая ошибка. Это будет работать для латинских символов (да и то не во всех случаях!), а для нелатинских может работать лишь по счастливой случайности. Правильный вариант - сделать на основе InputStream экземпляр Reader, с указанием кодировки (new InputStreamReader(inputStream, "<имя кодировки>")), и читать уже из него.

Это всё. Всем спасибо!

С уважением, Евгений aka Skipy

Поделиться:

Похожие статьи: