?

Log in

Previous Entry | Next Entry

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

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

Итак, код:

// входной поток, получается откуда-то извне
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. Дальше начинается цикл чтения. Он бесконечный, выход реализован внутри. В цикле мы делаем следующее:

    1. Читаем данные в буфер с помощью метода is.read(byte[] buffer). В этом методе будет прочитано от 0 до buffer.length байт. Это надо запомнить на всю жизнь - никто не гарантирует вычитывания всего буфера, даже если данных достаточно! Реальное количество прочитанных байтов возвращается из метода как значение.

    2. Если реальное количество прочитанных байтов равно -1, это означает, что данные закончились. На это можно полагаться. В этом случае мы прерываем выполнение цикла чтения.

    3. Если прочитано больше нуля байтов (а может быть и ноль!) - мы записываем данные с помощью метода класса OutputStream write(byte[] buffer, int position, int length). Обратите внимание - поскольку буфер может быть заполнен при последнем чтении не полностью, необходимо указывать, сколько байтов из него надо взять.

    4. Продолжаем выполнение цикла.


  4. Сбрасываем остаток данных в поток и закрываем его

  5. Получаем данные в виде массива байтов

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

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

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

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

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



P.S. Комментарии? Дополнения?

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

Comments

magicprinc
Jun. 28th, 2010 07:15 am (UTC)
Считаете надо это логировать?

Я убрал в конце концов:

public static void flushAndClose (@Nullable Closeable someStreamOrWriter) {
if (someStreamOrWriter != null) {
if (someStreamOrWriter instanceof Flushable) {//может УЖЕ быть закрыт => ожидаем IOException
try { ((Flushable) someStreamOrWriter).flush(); } catch (Throwable ignore) {}
}//i can flush

try {
someStreamOrWriter.close();
} catch (Throwable ignore) {}
}//i
}//flushAndClose


Случается тфу^3 редко, а когда случается - в общем объеме логов не видно.
magicprinc
Jun. 28th, 2010 07:16 am (UTC)
В любом случае неясно в общем случае, что делать если там случится exception...

В редких случаях, когда можно ситуацию исправить - предлагается делать это вручную, а не с помощью "тихого" универсального метода.