在Java中将InputStream转换为字节数组

1001

我该如何将整个InputStream读入字节数组中?


18
请参考以下链接了解如何将 byte[] 转换为 InputStream:https://dev59.com/questions/AHI95IYBdhLWcg3w7CnL。 - David d C e Freitas
35个回答

1277

您可以使用Apache Commons IO来处理这个和类似的任务。

IOUtils类型有一个静态方法,用于读取InputStream并返回byte[]

InputStream is;
byte[] bytes = IOUtils.toByteArray(is);

它在内部创建一个ByteArrayOutputStream并将字节复制到输出中,然后调用toByteArray()。它通过按块大小为4KiB复制字节来处理大文件。


224
你觉得引入第三方依赖库就值得为了写四行代码而烦恼吗? - oxbow_lakes
240
如果有一个处理要求、处理大文件并经过充分测试的库,那么问题就是为什么我要自己编写呢?这个jar包只有107KB,如果你需要其中的一个方法,你很可能也会用到其他方法。 - Rich Seller
263
考虑到我在开发人员生涯中看到的大量错误实现,我认为确实非常值得依赖外部库来正确实现这个功能。 - Joachim Sauer
20
为什么不去看看Apache Commons的一些东西,比如FastArrayList或他们的软/弱引用映射,然后回来告诉我这个库有多"经过充分测试"。它是一堆垃圾。 - oxbow_lakes
93
除了Apache commons-io之外,还可以查看Google Guava的ByteStreams类。InputStream is; byte[] filedata=ByteStreams.toByteArray(is); - michaelok
显示剩余19条评论

494

你需要从InputStream中读取每个字节并将其写入ByteArrayOutputStream中。

然后,通过调用toByteArray()方法可以检索底层的字节数组:

InputStream is = ...
ByteArrayOutputStream buffer = new ByteArrayOutputStream();

int nRead;
byte[] data = new byte[16384];

while ((nRead = is.read(data, 0, data.length)) != -1) {
  buffer.write(data, 0, nRead);
}

return buffer.toByteArray();

23
新创建的byte[]的大小是16384,为什么是这个值?我该如何确定精确的正确大小?非常感谢。 - Ondrej Bozek
6
16384是一个相当随意的选择,尽管我倾向于使用2的幂来增加数组与单词边界对齐的机会。pihentagy的回答展示了如何避免使用中间缓冲区,而是分配正确大小的数组。除非你正在处理大文件,否则我个人更喜欢上面的代码,它更加优雅,并且可以用于InputStreams,其中读取的字节数事先未知。 - Adamski
@Adamski,创建字节数组比你预期的流数据要大得多,这不会浪费内存吗? - Paweł Brewczynski
@ClayFerguson:有趣;你知道为什么4K在常见情况下被认为是最佳的吗? - Adamski
6
很多基础设施硬件、Web服务器和操作系统层组件都使用4K缓冲区来移动数据,所以这就是确切数字的原因,但主要问题在于,通过超过4K获取的性能提升很小,通常被认为是浪费内存。我假设这仍然是正确的,因为这是我十年前学到的知识! - user2080225
显示剩余13条评论

464

2
Java文档:“请注意,此方法适用于简单情况,其中将所有字节读入字节数组很方便。它不适用于读取大量数据的输入流。”实际上,从我的测试中可以看出,它被限制在8192个字节(未记录)。请在测试中使用它,但不要在生产中使用。 - pdem
4
@pdem,没有这样的限制。我刚刚通过将一个2GiB文件读入内存来验证了它。只要可以分配足够大的数组,它就能工作。如果你得到了不同的结果,那就是测试设置的问题。不应该读取如此大的文件到内存中,而是应该在读取时处理它们,这是完全不同的事情。显然,这适用于Q&A中提出的所有解决方案,因为它们都涉及将整个文件读入内存。 - Holger
你很反应迅速,我测试过2个JDK(11 17),并且像你说的一样可以用一个大的byteInputstream工作,所以我猜可能是我的API有问题,而这个API是JavaMail:我会从MimeMessage内容中获取文件。奇怪的是,与经典手动读取相关的bug在使用JavaMail时不会出现。 - pdem
1
这个特定的InputStream是否覆盖了readAllBytes()readNBytes方法? - Holger
是的和不是的,它是一个BASE64DecoderStream,它委托给一个SharedByteArrayInputStream,该流扩展了ByteArrayInputStream并覆盖了readAllBytes方法。我在多个代码委托中迷失了方向。 无论如何,我能够在单元测试中隔离出BASE64DecoderStream的错误。我知道这个错误不是来自JDK,但由于BASE64DecoderStream的实现,我仍然需要一个解决方法。感谢您的时间! - pdem
2
听起来好像值得单独提出一个问题。 - Holger

141

使用纯Java的 DataInputStream 和它的 readFully 方法(至少从 Java 1.4 开始存在):

...
byte[] bytes = new byte[(int) file.length()];
DataInputStream dis = new DataInputStream(new FileInputStream(file));
dis.readFully(bytes);
...

还有其他一些方法,但我经常用这种方法来处理这种情况。


51
使用标准库而不是第三方依赖项值得肯定。不幸的是,它对我没有用,因为我不知道流的长度。 - Andrew Spencer
4
@janus,这是一个“文件”。只有在您知道文件长度或要读取的字节数的情况下,此方法才有效。 - dermoritz
6
有趣的事情,但你必须知道要读取的(部分)流的确切长度。此外,类“DataInputStream”主要用于从流中读取基本类型(Long、Short、Char...),因此我们可以将这种用法视为对该类的误用。 - Olivier Faucheux
20
如果您已经知道从流中读取的数据长度,那么这种方法并不比InputStream.read更好。 - Logan Pickup
4
InputStream.read 方法不能保证返回您所请求的所有字节! - Ray Hulha
显示剩余7条评论

139

如果你碰巧使用Google Guava,那么使用ByteStreams就像呼吸一样简单:

byte[] bytes = ByteStreams.toByteArray(inputStream);

83

安全解决方案(正确关闭流):

  • Java 9及以上版本:

     final byte[] bytes;
     try (inputStream) {
         bytes = inputStream.readAllBytes();
     }
    

  • Java 8及更早版本:

 public static byte[] readAllBytes(InputStream inputStream) throws IOException {
     final int bufLen = 4 * 0x400; // 4KB
     byte[] buf = new byte[bufLen];
     int readLen;
     IOException exception = null;

     try {
         try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
             while ((readLen = inputStream.read(buf, 0, bufLen)) != -1)
                 outputStream.write(buf, 0, readLen);

             return outputStream.toByteArray();
         }
     } catch (IOException e) {
         exception = e;
         throw e;
     } finally {
         if (exception == null) inputStream.close();
         else try {
             inputStream.close();
         } catch (IOException e) {
             exception.addSuppressed(e);
         }
     }
 }

  • Kotlin(当Java 9+不可用时):

 @Throws(IOException::class)
 fun InputStream.readAllBytes(): ByteArray {
     val bufLen = 4 * 0x400 // 4KB
     val buf = ByteArray(bufLen)
     var readLen: Int = 0

     ByteArrayOutputStream().use { o ->
         this.use { i ->
             while (i.read(buf, 0, bufLen).also { readLen = it } != -1)
                 o.write(buf, 0, readLen)
         }

         return o.toByteArray()
     }
 }

为了避免嵌套的use,请查看此处


  • Scala(当Java 9+不可用时)(由@Joan提供。感谢):

def readAllBytes(inputStream: InputStream): Array[Byte] =
  Stream.continually(inputStream.read).takeWhile(_ != -1).map(_.toByte).toArray

这难道不意味着在某个时候你会使用双倍的内存,因为你既有缓冲区又有字节数组吗?难道没有一种方法可以直接将字节发送到输出字节数组中吗? - android developer
@androiddeveloper;对不起,我不知道答案!但我认为这种方式(使用缓冲区)是一种优化的方式。 - Mir-Ismaili
我已经检查过了,它是可以的,但似乎这是你在不知道大小时唯一可以选择的解决方案。如果你已经知道大小,就可以直接使用给定大小创建字节数组并进行填充。因此,你可以使用一个函数来获取字节大小的参数,如果有效,就可以直接创建和填充字节数组,而不需要创建任何其他大对象。 - android developer
@androiddeveloper;感谢您的信息,我之前不知道这些。 - Mir-Ismaili
2
Java 8的代码版本,也适用于Java 1.7,并且已经成功运行。 - LKF
为什么要关闭ByteArrayOutputStream?这是不正确的做法,并且会让阅读代码的人误以为需要关闭它,尽管关闭ByteArrayOutputStream并没有任何作用。实际上,声明ByteArrayOutputStream的整个try块都应该重写。那里根本不需要一个try块... - Kröw

68

一如既往,也Spring框架(自3.2.2起的spring-core)为你提供了以下方法:StreamUtils.copyToByteArray()


2
和大多数人一样,我想避免使用第三方库来完成这么简单的事情,但目前还不能使用Java 9...幸运的是,我已经在使用Spring了。 - scottysseus

48
public static byte[] getBytesFromInputStream(InputStream is) throws IOException {
    ByteArrayOutputStream os = new ByteArrayOutputStream(); 
    byte[] buffer = new byte[0xFFFF];
    for (int len = is.read(buffer); len != -1; len = is.read(buffer)) { 
        os.write(buffer, 0, len);
    }
    return os.toByteArray();
}

2
这只是一个例子,因此简洁明了才是今天的主旨。在某些情况下,返回null会是适当的选择(尽管在生产环境中,您也需要正确的异常处理和文档记录)。 - user2403009
13
明白在示例代码中使用简洁的风格,但为什么不直接让示例方法抛出IOException异常,而是要吞噬它并返回一个无意义的值呢? - pendor
5
我已经擅自更改了代码中的 'return null' 为 'throw IOException'。 - kritzikratzi
4
不需要使用Try-with-resources,因为ByteArrayOutputStream#close()不做任何事情。(ByteArrayOutputStream#flush()同样不需要且什么也不做。) - Luke Hutchison

21

如果有人仍在寻找一种无依赖性的解决方案,而且您有一个文件,则可以考虑使用以下代码:

DataInputStream

 byte[] data = new byte[(int) file.length()];
 DataInputStream dis = new DataInputStream(new FileInputStream(file));
 dis.readFully(data);
 dis.close();

ByteArrayOutputStream

 InputStream is = new FileInputStream(file);
 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
 int nRead;
 byte[] data = new byte[(int) file.length()];
 while ((nRead = is.read(data, 0, data.length)) != -1) {
     buffer.write(data, 0, nRead);
 }
随机访问文件
 RandomAccessFile raf = new RandomAccessFile(file, "r");
 byte[] data = new byte[(int) raf.length()];
 raf.readFully(data);

假设字节数组太大,可能会导致堆内存溢出,那么有没有类似的解决方案可以使用JNI来存储字节,然后我们可以从那里存储的数据(一种临时缓存)中使用inputStream? - android developer
抱歉,我不小心给这个答案点了踩。你能否编辑一些字符,以便我可以撤销这个点击?谢谢! - Michael Ouyang
谢谢,@MichaelOuyang。希望我的回答有所帮助 :) - harsh_v

19

你真的需要将图像作为byte[]吗? 你期望在byte[]中得到什么 - 图像文件的完整内容,以任何格式编码的,还是RGB像素值?其他答案展示了如何将文件读入byte[]。你的byte[]将包含完整的文件内容,你需要解码才能对图像数据进行处理。

Java的标准API用于读取(和写入)图像是ImageIO API,在javax.imageio包中。你可以使用一行代码从文件中读取图像:

BufferedImage image = ImageIO.read(new File("image.jpg"));
这将给你一个BufferedImage,而不是一个byte[]。要访问图像数据,你可以在BufferedImage上调用getRaster()。这将给你一个Raster对象,它有许多方法来访问像素数据(包括几个getPixel()/getPixels()方法)。
查找javax.imageio.ImageIOjava.awt.image.BufferedImagejava.awt.image.Raster等API文档。
ImageIO默认支持多种图像格式:JPEG、PNG、BMP、WBMP和GIF。还可以添加更多格式的支持(需要实现ImageIO服务提供者接口的插件)。
另请参阅以下教程:处理图像

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