Java:从InputStream中读取数据时,并不总是读取相同数量的数据。

5

不管是好是坏,我一直在使用以下类似的代码,而没有遇到任何问题:

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

我曾经在文件I/O中使用了这个(获取可用空间并直接读取到字节缓冲区的逻辑),并且也与zip文件一起使用没有遇到任何问题。
但最近我遇到一个情况,zipInput.read(content, 0, theSize);实际上比可用的theSize少读了3个字节。
由于代码不在循环中检查zipInput.read(content, 0, theSize);返回的长度,所以我读取文件时漏掉了最后3个字节,并且后来程序无法正常运行(文件是二进制文件)。
奇怪的是,对于不同的zip文件(在我的情况下,有问题的zip条目为867字节),比如1075字节的大型zip文件,代码可以正常工作!
我明白代码的逻辑可能不是“最好”的,但为什么我现在突然遇到了这个问题?
而且如果我立即使用较大的zip条目运行程序,它为什么能够正常运行?
非常欢迎任何输入。
谢谢!
3个回答

7

InputStreamread API 文档中可知:

尝试读取长度最多为 len 个字节,但可能读取较少的数量。

... 并且:

返回:已读入缓冲区的总字节数,如果已到达流的末尾,则返回 -1。

换言之,除非 read 方法返回 -1,否则仍有更多数据可供读取,但不能保证 read 将精确地读取指定数量的字节。指定数量的字节是描述它将读取的最大数据量的上限。


我了解此事。这就是为什么我提到我的方法不太好的原因。尽管如此,我正在努力理解这种行为,在一个小文件中缺少最后3个字节,但在较大的文件中没有任何问题。 - Cratylus
3
理解特定行为没有目标:它取决于实现方式,你的代码可能因许多因素而出现各种问题。重要的是要理解代码可能出现的一般性错误以及如何修复它们。 - Joachim Sauer

2
使用available()不能保证计算到流的end of stream总可用字节数。
请参考Java InputStreamavailable()方法。它说:

返回可以从此输入流读取(或跳过)而不被下一次调用此输入流方法阻塞的字节数的估计值。下一次调用可能是相同的线程或另一个线程。单个读取或跳过这么多字节不会阻塞,但可能读取或跳过较少的字节。

请注意,虽然某些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  
...   

这适用于所有大小的输入文件。


5
请完全不要使用“available”这个词!它没什么用,只会让你的代码更加脆弱。只需一直读取内容,直到没有更多内容为止。 - Joachim Sauer
1
@Joachim:available 什么时候使用? - Cratylus
2
@user384706:许多InputStream实现都有一些“良好的场景”,在这些场景中它们似乎可以正常工作。小文件、大文件、扇区对齐的文件、网络MTU大小等等,所有这些情况下代码似乎都能正常运行。但是,一旦其中一个因素出了问题,你的代码就会突然产生错误的输出。 - Joachim Sauer
1
@user384706:这是一种危险的“方便”方式:available大小可能是一个与实际需求无关的任意数字。如果你的目标是读取整个文件,那么你需要一个更大的缓冲区。如果你的目标是尽可能快地读取,那么你的缓冲区应该是一些合理的、恒定的大小,以避免重新分配它,如果你的目标是读取“一组信息”(在某个定义的协议中),那么你需要确保你读取恰好那么多字节。 - Joachim Sauer
1
@Ravinder:它可能会导致不同的行为。他们定义中唯一相关的区别是“估计”这个词。 - Joachim Sauer
显示剩余23条评论

0

做到这一点的最佳方式应该是如下:

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,然后返回字节数组。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接