Java InputStream 读取问题

Posted

技术标签:

【中文标题】Java InputStream 读取问题【英文标题】:Java InputStream reading problem 【发布时间】:2011-09-03 20:43:31 【问题描述】:

我有一个 Java 类,我通过 InputStream 读取数据

    byte[] b = null;
    try 
        b = new byte[in.available()];
        in.read(b);
     catch (IOException e) 
        e.printStackTrace();
    

当我从 IDE (Eclipse) 运行我的应用程序时,它可以完美运行。

但是当我导出我的项目并将它打包在一个 JAR 中时,读取命令不会读取所有数据。我该如何解决?

这个问题主要发生在 InputStream 是一个文件 (~10kb) 时。

谢谢!

【问题讨论】:

所以看起来你运行 eclipse 的操作系统在调用 available() 时返回了文件的总大小,但这在你的测试盒上没有发生。所以不要像java doc所说的那样依赖available()返回的数字。 available() 的经典误用。 Javadoc 中有一个警告特别反对以这种方式使用它。 【参考方案1】:

通常我更喜欢在从输入流中读取时使用固定大小的缓冲区。正如 evilone 指出的那样,使用 available() 作为缓冲区大小可能不是一个好主意,因为例如,如果您正在读取远程资源,那么您可能事先不知道可用字节。您可以阅读 InputStream 的 javadoc 以获得更多信息。

这里是我通常用来读取输入流的代码sn-p:

byte[] buffer = new byte[BUFFER_SIZE];

int bytesRead = 0;
while ((bytesRead = in.read(buffer)) >= 0)
  for (int i = 0; i < bytesRead; i++)
     //Do whatever you need with the bytes here
  

我在这里使用的 read() 版本将尽可能地填充给定的缓冲区,并且 返回实际读取的字节数。这意味着您的缓冲区可能包含尾随垃圾数据,因此仅使用最多 bytesRead 的字节非常重要。

注意(bytesRead = in.read(buffer)) &gt;= 0这一行,InputStream 规范中没有任何内容说 read() 不能读取 0 字节。 您可能需要处理 read() 读取 0 字节的特殊情况情况取决于你的情况。对于本地文件,我从未遇到过这种情况;但是,在读取远程资源时,我实际上看到 read() 不断读取 0 个字节,导致上述代码进入无限循环。我通过计算读取 0 字节的次数来解决无限循环问题,当计数器超过阈值时,我将抛出异常。你可能不会遇到这个问题,但请记住这一点:)

出于性能原因,我可能不会为每次读取创建新的字节数组。

【讨论】:

谢谢,这是一个非常好的解决方案! InputStream.read() 的 Javdoc 明确指出它会阻塞,直到至少传输一个字节或流结束或发生异常。它可以返回零的唯一方法是提供零长度缓冲区或计数。 @EJP 感谢您指出这一点,当我发布此答案时,我完全错过了这一点。但是,检查 read(...) 返回 0 仍然很重要,因为根据实际事件,即使给定长度大于 0 的缓冲区,某些框架也会返回 read(...) 返回 0 的 InputStream 实现。 @EJP 但我在实际场景中面临读取 0 字节(同时读取 zip 文件内容)......所以我总是最好检查 bytesRead &gt; 0 我猜......跨度> 【参考方案2】:

当 InputStream 耗尽时,read() 将返回 -1。还有一个版本的read 接受一个数组,这允许您进行分块读取。它在 InputStream 结束时返回实际读取的字节数或-1。将此与动态缓冲区(例如 ByteArrayOutputStream)结合起来,得到以下结果:

InputStream in = ...
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int read;
byte[] input = new byte[4096];
while ( -1 != ( read = in.read( input ) ) ) 
    buffer.write( input, 0, read );

input = buffer.toByteArray()

这大大减少了您必须调用的方法数量,并允许 ByteArrayOutputStream 更快地增长其内部缓冲区。

【讨论】:

【参考方案3】:
File file = new File("/path/to/file");

try 
   InputStream is = new FileInputStream(file);
   byte[] bytes = IOUtils.toByteArray(is);

   System.out.println("Byte array size: " + bytes.length);
 catch (IOException e) 
   e.printStackTrace();

【讨论】:

"请注意,虽然 InputStream 的某些实现会返回流中的总字节数,但很多不会。使用此方法的返回值来分配一个打算保存的缓冲区是不正确的此流中的所有数据。”但是,我应该使用什么来代替可用? 您可以直接获取文件的长度,并且您应该始终在循环中read()(可能不是像这个答案那样逐字节)并检查返回了多少字节。 read(byte[]) 不能保证读取任意数量的字节。 AFAIK toByteArray() 为您从输入流中读取。第一行之后的代码就不需要了。【参考方案4】:

下面是一段代码,用于下载文件(*.Png、*.Jpeg、*.Gif、...)并将其写入代表 HttpServletResponse 的 BufferedOutputStream。

BufferedInputStream inputStream = bo.getBufferedInputStream(imageFile);
try 
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    int bytesRead = 0;
    byte[] input = new byte[DefaultBufferSizeIndicator.getDefaultBufferSize()];
    while (-1 != (bytesRead = inputStream.read(input))) 
        buffer.write(input, 0, bytesRead);
    
    input = buffer.toByteArray();

    response.reset();
    response.setBufferSize(DefaultBufferSizeIndicator.getDefaultBufferSize());
    response.setContentType(mimeType);
    // Here's the secret. Content-Length should equal the number of bytes read.
    response.setHeader("Content-Length", String.valueOf(buffer.size()));
    response.setHeader("Content-Disposition", "inline; filename=\"" + imageFile.getName() + "\"");

    BufferedOutputStream outputStream = new BufferedOutputStream(response.getOutputStream(), DefaultBufferSizeIndicator.getDefaultBufferSize());
    try 
        outputStream.write(input, 0, buffer.size());
     finally 
        ImageBO.close(outputStream);
    
 finally 
    ImageBO.close(inputStream);

希望这会有所帮助。

【讨论】:

感谢您的回答,内容应以英文而非葡萄牙语发布。我已更新您的答案以删除葡萄牙语版本。 ByteArrayOutputStream 完全是浪费时间和空间。输入应直接写入输出。您根本不需要设置内容长度。

以上是关于Java InputStream 读取问题的主要内容,如果未能解决你的问题,请参考以下文章

java InputStream读取数据问题

Java InputStream 读取问题

Java如何从InputStream中读取收入大小? [复制]

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

Java InputStream 阻塞读取

如何在 Java 中将 InputStream 读取/转换为字符串?