Java InputStream 读取问题

10

我有一个Java类,在其中通过InputStream读取数据。

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

当我从IDE(Eclipse)运行我的应用程序时,它完全正常。

但是,当我导出项目并将其打包成JAR文件时,read命令无法读取所有数据。我该如何解决?

这个问题通常发生在InputStream为File(约10kb)的情况下。

谢谢!


1
看起来你运行Eclipse的操作系统在调用available()时返回了文件的总大小,但是在你的测试环境中并没有发生这种情况。因此,不要依赖于available()返回的数字,正如Java文档中所说的那样。 - Adrian Liu
1
available() 的经典误用。Javadoc 中明确警告不要以这种方式使用它。 - user207421
4个回答

8
通常我更喜欢使用固定大小的缓冲区从输入流中读取。正如evilone所指出的,使用available()作为缓冲区大小可能不是一个好主意,因为如果你正在读取远程资源,那么你可能不知道可用字节数。您可以阅读InputStream的javadoc以获取更多见解。
这是我通常用于读取输入流的代码片段:
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)) >= 0InputStream规范中没有说read()不能读取0个字节的内容。根据您的情况,您可能需要处理read()读取0个字节的情况。对于本地文件,我从未遇到过这种情况;但是,在读取远程资源时,我确实看到read()不断地读取0个字节,导致上述代码进入无限循环。我通过计算读取0个字节的次数来解决了无限循环问题,当计数器超过阈值时,我会抛出异常。您可能不会遇到此问题,但请记住这一点:)

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


2
InputStream.read() 的 Javdoc 明确说明它会阻塞,直到至少传输一个字节、流结束或发生异常。它能够返回零的唯一方式是如果您提供了一个零长度的缓冲区或计数。 - user207421
@EJP 感谢您指出这一点,当我发布这个答案时我完全没有注意到。然而,仍然很重要要检查read(...)是否返回0,因为根据实际事件,一些框架即使给定一个长度大于0的缓冲区,也会返回InputStream的实现,其中read(...)返回0。 - Alvin
@EJP但是在实际场景中,我遇到过读取0字节(读取zip文件内容时)的情况...所以我想最好检查bytesRead > 0吧... - Tom Taylor

7
当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 更快地增大其内部缓冲区。


4
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的实现会返回流中的总字节数,但许多实现不会这样做。使用该方法的返回值来分配一个用于容纳流中所有数据的缓冲区是错误的。那么,在没有Available方法的情况下,应该使用什么呢? - Michael
你可以直接获取文件的长度,而且你应该总是在循环中使用read()(可能不是像这个答案一样逐字节读取),并检查返回了多少字节。 read(byte[])不能保证读取您想要的那么多字节。 - David Harkness
据我所知,toByteArray() 会为您从输入流中读取数据。第一行之后的代码是不必要的。 - David Harkness

0
以下是一段代码片段,用于下载文件(*.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);
}

希望这能有所帮助。

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

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