Java: чтение из InputStream не всегда считывает одинаковый объем данных

#java #jakarta-ee #file-io #zip #inputstream

#java #jakarta-ee #file-io #zip #inputstream

Вопрос:

Хорошо это или плохо, я без проблем использую код, подобный следующему:

 ZipFile aZipFile = new ZipFile(fileName);   
InputStream zipInput = aZipFile.getInputStream(name);  
int theSize = zipInput.available();  
byte[] content = new byte[theSize];  
zipInput.read(content, 0, theSize);
 

Я использовал это (эта логика получения доступного размера и чтения непосредственно в байтовый буфер)
для File ввода-вывода без каких-либо проблем, и я также использовал его с zip-файлами.

Но недавно я столкнулся с тем, что zipInput.read(content, 0, theSize); фактически считывается на 3 байта меньше, чем theSize доступно.

И поскольку код не находится в цикле для проверки длины, возвращаемой zipInput.read(content, 0, theSize); I, я прочитал файл с пропущенными 3 последними байтами
, и позже программа не может функционировать должным образом (файл является двоичным файлом).).

Как ни странно, с разными zip-файлами большего размера, например, 1075 байт (в моем случае проблемная zip-запись составляет 867 байт), код работает нормально!

Я понимаю, что логика кода, вероятно, не самая «лучшая», но почему у меня вдруг возникла эта проблема сейчас?

И почему, если я немедленно запускаю программу с большей zip-записью, она работает?

Любой ввод приветствуется

Спасибо

Ответ №1:

Из InputStream read документов API:

Предпринимается попытка прочитать столько байтов, сколько len, но может быть прочитано меньшее число.

… и:

Возвращает: общее количество байтов, считанных в буфер, или -1, если данных больше нет, поскольку достигнут конец потока.

Другими словами, если метод read не возвращает -1, для чтения доступно еще больше данных, но вы не можете гарантировать, что read они будут считывать точно указанное количество байтов. Указанное количество байтов является верхней границей, описывающей максимальный объем данных, которые он будет считывать.

Комментарии:

1. Я знаю об этом. Вот почему я упомянул, что мой подход не так хорош. Тем не менее, я пытаюсь понять это поведение, из-за отсутствия последних 3 байтов в небольшом файле, но без каких-либо проблем в больших файлах

2. @user384706: понимание того, что конкретное поведение не служит цели: оно зависит от реализации, и ваш код может пойти не так по-разному, в зависимости от многих факторов. Важно понимать, почему ваш код может пойти не так в целом и как это исправить.

Ответ №2:

Использование available() не гарантирует, что оно подсчитало общее количество доступных байтов для end of stream .
Обратитесь к методу Java InputStream . available() В нем говорится, что

Возвращает оценку количества байтов, которые могут быть прочитаны (или пропущены) из этого входного потока без блокировки при следующем вызове метода для этого входного потока. Следующий вызов может быть тем же потоком или другим потоком. Одно чтение или пропуск такого количества байтов не блокируется, но может считывать или пропускать меньше байтов.

Обратите внимание, что, хотя некоторые реализации InputStream будут возвращать общее количество байтов в потоке, многие этого не сделают. Неверно использовать возвращаемое значение этого метода для выделения буфера, предназначенного для хранения всех данных в этом потоке.

Пример решения вашей проблемы может быть следующим:

 ZipFile aZipFile = new ZipFile(fileName);   
InputStream zipInput = aZipFile.getInputStream( caImport );  
int available = zipInput.available();  
byte[] contentBytes = new byte[ available ];  
while ( available != 0 )   
{   
    zipInput.read( contentBytes );   
    // here, do what ever you want  
    available = dis.available();  
} // while available  
...   
 

Это точно работает для всех размеров входных файлов.

Комментарии:

1. Пожалуйста, не используйте available для этого вообще ! Это не помогает и только делает ваш код более хрупким. Просто читайте, пока не получите больше содержимого.

2. @Joachim: Итак, когда available используется?

3. @user384706: единственное допустимое использование, о котором я знаю, — это если вы хотите проверить медленный поток (например, сокет), чтобы узнать, есть ли вообще какие-либо данные (и если ничего не доступно, сделайте что-нибудь еще). Причина, по которой это бесполезно ни для чего другого, заключается в том, что оно по своей сути является пикантным (т. Е. К тому времени, когда вы выполняете фактическое read() число, возможно, уже изменилось).

4. @user384706: во многих InputStream реализациях есть несколько «хороших сценариев», в которых они будут работать. Маленькие файлы, большие файлы, файлы с выравниванием по секторам, MTU-размеры в сети. Все это могут быть случаи, когда код кажется нормальным. И как только один из этих факторов выходит из строя, ваш код внезапно выдает неверный вывод.

5. @user384706: это опасный вид «условности»: available размер может быть произвольным числом, которое не имеет ничего общего с тем, что вам действительно нужно. Если ваша цель — прочитать весь файл, вам понадобится буфер большего размера. Если ваша цель — читать как можно быстрее, то ваш буфер должен быть разумного постоянного размера, чтобы избежать необходимости его перераспределения, и если ваша цель — прочитать «один пакет информации» (в каком-то определенном протоколе), тогда вам нужно убедиться, что вы прочитали именно столько байтов.

Ответ №3:

Лучший способ сделать это должен быть следующим:

 public static byte[] readZipFileToByteArray(ZipFile zipFile, ZipEntry entry)
    throws IOException {
    InputStream in = null;
    try {
        in = zipFile.getInputStream(entry);
        return IOUtils.toByteArray(in);
    } finally {
        IOUtils.closeQuietly(in);
    }
}
 

где IOUtils.Метод toByteArray (in) продолжает чтение до EOF, а затем возвращает массив байтов.