将java.net.URL读取流转换为byte[]

51

我正在尝试使用Java的java.net.URL包将URL中的图像读取为byte[]。除了流未完全读取内容(图像损坏,不包含所有图像数据)之外,“一切”都正常工作...该字节数组被持久化在数据库(BLOB)中。我真的不知道正确的方法是什么,也许你可以给我一些提示。:)

这是我的第一个尝试(代码格式化,删除了不必要的信息...):

URL u = new URL("http://localhost:8080/images/anImage.jpg");
int contentLength = u.openConnection().getContentLength();
Inputstream openStream = u.openStream();
byte[] binaryData = new byte[contentLength];
openStream.read(binaryData);
openStream.close();

我的第二种方法是这样的(你会看到contentlength被另一种方式获取):

URL u = new URL(content);
openStream = u.openStream();
int contentLength = openStream.available();
byte[] binaryData = new byte[contentLength];
openStream.read(binaryData);
openStream.close();

这两段代码都会导致图像损坏...我已经阅读了来自Stack Overflow的帖子。

8个回答

66

无法保证您提供的内容长度是准确的。尝试类似以下的方法:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream is = null;
try {
  is = url.openStream ();
  byte[] byteChunk = new byte[4096]; // Or whatever size you want to read in at a time.
  int n;

  while ( (n = is.read(byteChunk)) > 0 ) {
    baos.write(byteChunk, 0, n);
  }
}
catch (IOException e) {
  System.err.printf ("Failed while reading bytes from %s: %s", url.toExternalForm(), e.getMessage());
  e.printStackTrace ();
  // Perform any other exception handling that's appropriate.
}
finally {
  if (is != null) { is.close(); }
}

接下来,你将在baos中获得图像数据,通过调用baos.toByteArray()可以获得一个字节数组。

这段代码未经测试(我只是在答案框中编写),但它是我认为符合你需求的一种合理近似。


48
请勿编写空的catch块,即使是在示例中!请至少加上e.printStackTrace()!示例往往会变成生产代码,我们所有人以后都必须使用它。 - Joachim Sauer
4
你说得完全正确,感谢指出。我已经在示例中添加了更有意义的异常处理。 - RTBarnard
使用http://commons.apache.org/io/api-1.4/org/apache/commons/io/IOUtils.html#toByteArray(java.io.InputStream)。这将使代码看起来更加清晰。 - Adisesha
谢谢你的回答,你的方法确实有效 :) 我必须逐位(或按定义的块值)将数据写入 ByteArrayOutputStream 中(最终将使用 .toByteArray() 输出),这是我的错误... - tim.kaufner
在我的应用程序中,这段代码在某些情况下会卡在is.read(byteChunk)上,永远不会继续执行。 - Roel
我修改了 in = new BufferedInputStream(url.openStream());,现在它可以正常工作而不会挂起。 - Roel

35

使用commons-io扩展Barnards的答案。之所以单独回答是因为我无法在评论中格式化代码。

InputStream is = null;
try {
  is = url.openStream ();
  byte[] imageBytes = IOUtils.toByteArray(is);
}
catch (IOException e) {
  System.err.printf ("Failed while reading bytes from %s: %s", url.toExternalForm(), e.getMessage());
  e.printStackTrace ();
  // Perform any other exception handling that's appropriate.
}
finally {
  if (is != null) { is.close(); }
}

http://commons.apache.org/io/api-1.4/org/apache/commons/io/IOUtils.html#toByteArray(java.io.InputStream)


这是另一个不错的解决方案,但我会使用第一个(因为我们不想包含太多外部库)。 - tim.kaufner
这个解决方案对我无效 - 服务器返回HTTP响应代码:403,URL为:http://templatelab.com/wp-content/uploads/2015/11/Coupon-50.jpg - Javo

27

这里有一个简洁的解决方案:

private byte[] downloadUrl(URL toDownload) {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

    try {
        byte[] chunk = new byte[4096];
        int bytesRead;
        InputStream stream = toDownload.openStream();

        while ((bytesRead = stream.read(chunk)) > 0) {
            outputStream.write(chunk, 0, bytesRead);
        }

    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }

    return outputStream.toByteArray();
}

10
别忘了调用“stream”的close方法来释放已使用的资源。 - Vini.g.fer

16

我很惊讶这里没有人提到“连接和读取超时”的问题。这可能会发生(尤其是在 Android 或某些烂网络连接情况下),请求可能会一直挂起等待。

以下代码(还使用了 Apache IO Commons)考虑到了这一点,并等待最多 5 秒钟直到失败:

public static byte[] downloadFile(URL url)
{
    try {
        URLConnection conn = url.openConnection();
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(5000);
        conn.connect(); 

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        IOUtils.copy(conn.getInputStream(), baos);

        return baos.toByteArray();
    }
    catch (IOException e)
    {
        // Log error and return null, some default or throw a runtime exception
    }
}

15

使用 commons-io IOUtils.toByteArray(URL) 方法:

String url = "http://localhost:8080/images/anImage.jpg";
byte[] fileContent = IOUtils.toByteArray(new URL(url));

Maven 依赖:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

12
byte[] b = IOUtils.toByteArray((new URL( )).openStream()); //idiom

需要注意的是,上述示例中流并未关闭。

如果你想使用commons codec获取一个长度为76个字符的块...

byte[] b = Base64.encodeBase64(IOUtils.toByteArray((new URL( )).openStream()), true);

1

内容长度只是HTTP头部信息,不能完全信任它。应该从流中读取尽可能多的内容。

"可用"这个词肯定是不准确的。它只是指在不阻塞的情况下可以读取的字节数。

另一个问题是资源处理。无论如何都必须关闭流。使用try/catch/finally语句块可以实现这一点。


谢谢您的回答。我在我的代码发布中省略了try/catch。 但是我如何知道流的确切长度?我必须分配byte[],因此必须提供长度。分配固定长度(比如1024),从位置读取到偏移量,检查流是否包含数据,将其复制到新数组,合并所有byte[]可能不是最佳解决方案... - tim.kaufner

1

特别是当服务器响应时间较长时,指定超时时间非常重要。在使用纯Java而不使用任何依赖项的情况下:

public static byte[] copyURLToByteArray(final String urlStr,
        final int connectionTimeout, final int readTimeout) 
                throws IOException {
    final URL url = new URL(urlStr);
    final URLConnection connection = url.openConnection();
    connection.setConnectTimeout(connectionTimeout);
    connection.setReadTimeout(readTimeout);
    try (InputStream input = connection.getInputStream();
            ByteArrayOutputStream output = new ByteArrayOutputStream()) {
        final byte[] buffer = new byte[8192];
        for (int count; (count = input.read(buffer)) > 0;) {
            output.write(buffer, 0, count);
        }
        return output.toByteArray();
    }
}

使用依赖项,例如HC Fluent
public byte[] copyURLToByteArray(final String urlStr,
        final int connectionTimeout, final int readTimeout)
                throws IOException {
    return Request.Get(urlStr)
            .connectTimeout(connectionTimeout)
            .socketTimeout(readTimeout)
            .execute()
            .returnContent()
            .asBytes();
}

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