将InputStream转换为byte[]的最有效方法是什么?

12
ChannelBufferInputStream responseStream = (ChannelBufferInputStream) response.getBodyAsStream();
ArrayList<Byte> arrayList = new ArrayList<Byte>();
try {
    while (responseStream.available() > 0) {
        arrayList.add(responseStream.readByte());
    }
} catch (IOException e) {
    e.printStackTrace();
    return internalServerError();
}
Iterator<Byte> iterator = arrayList.iterator();
byte[] bytes = new byte[arrayList.size()];
int i = 0;
while (iterator.hasNext()) {
    bytes[i++] = iterator.next();
}

这段代码在我的Web应用的每个页面加载时都被调用。它似乎运行得很快,但是否有任何方法可以使它运行得更快?

编辑 - 使用字节数组输出流进行更新

ChannelBufferInputStream responseStream = (ChannelBufferInputStream) response.getBodyAsStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
    int read = responseStream.read();
    while (read != -1) {
        byteArrayOutputStream.write(read);
        read = responseStream.read();
    }
} catch (IOException e) {
    e.printStackTrace();
    return internalServerError();
}
byte[] bytes = byteArrayOutputStream.toByteArray();
return ok(bytes).as(response.getHeader("Content-type"));

编辑 - 基准测试代码

ChannelBufferInputStream responseStream = (ChannelBufferInputStream) response.getBodyAsStream();
long t1 = System.nanoTime();

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
    int read = responseStream.read();
    while (read != -1) {
        byteArrayOutputStream.write(read);
        read = responseStream.read();
    }
} catch (IOException e) {
    e.printStackTrace();
    return internalServerError();
}
byte[] bytes = byteArrayOutputStream.toByteArray();

long t2 = System.nanoTime();
System.out.println(t2-t1);
return ok(bytes).as(response.getHeader("Content-type"));

100多个请求后的平均时间 - 46873

ChannelBufferInputStream responseStream = (ChannelBufferInputStream) response.getBodyAsStream();
long t1 = System.nanoTime();

ArrayList<Byte> arrayList = new ArrayList<Byte>();
try {
    while (responseStream.available() > 0) {
        arrayList.add(responseStream.readByte());
    }
} catch (IOException e) {
    e.printStackTrace();
    return internalServerError();
}
Iterator<Byte> iterator = arrayList.iterator();
byte[] bytes = new byte[arrayList.size()];
int i = 0;
while (iterator.hasNext()) {
    bytes[i++] = iterator.next();
}

long t2 = System.nanoTime();
System.out.println(t2-t1);
return ok(bytes).as(response.getHeader("Content-type"));

100次以上请求后的平均时间 - 522848

long t1 = System.nanoTime();
byte[] bytes;
try {
    bytes = org.apache.commons.io.IOUtils.toByteArray(responseStream);
} catch (Exception e) {
    return internalServerError();
}

long t2 = System.nanoTime();
System.out.println(t2-t1);

100多个请求后的平均时间 - 45088

long t1 = System.nanoTime();
byte[] bytes;
try {
    bytes = sun.misc.IOUtils.readFully(responseStream, -1, true);
} catch (Exception e) {
    return internalServerError();
}

long t2 = System.nanoTime();
System.out.println(t2 - t1);

100次以上请求后的平均时间 - 20180


嘿,马特,我已经阅读了那篇文章。我正在寻求最大效率。 - sissonb
如果您非常关心这个问题,应该自己测量和比较不同的实现方法。然而,既然您说“运行得相当快”,那就不像是瓶颈了,如果您试图让代码运行更快。 - Matt Ball
看一下这个:https://dev59.com/-FnUa4cB1Zd3GeqPXRu1 - Tiago Almeida
@TiagoAlmeida 这里并不是很相关。 - Matt Ball
@MattBall 这段代码还没有部署到生产环境,但由于它会在每个页面加载时被调用,我希望它能尽可能快地运行,以保持页面加载时间的稳定。 - sissonb
sun.misc.IOUtils.readFully() 不再接受 -1 作为长度参数,至少在 AdoptOpenJDK 8u242 版本中是如此。 - Stefan L
3个回答

14

是的。使用ByteArrayOutputStream而不是ArrayList。然后从输入流中读取字节块(不使用available(),这几乎永远不应该使用),并将这些字节块写入到ByteArrayOutputStream中,直到read()方法返回-1。然后在ByteArrayOutputStream上调用toByteArray()

你可以使用Guava的ByteStreams.toByteArray()方法,它会为您完成所有这些操作,或者您也可以阅读其源代码以更好地了解它是如何实现的。阅读IO教程也可能有所帮助。


谢谢,让我测试一下。我还会进行一些基准测试。 - sissonb
available() 返回可以在不阻塞的情况下读取的字节数。它可能始终返回0(默认情况下是这样)。 - JB Nizet
看到一些速度的提升,谢谢。 - sissonb
你有什么想法,为什么 sun.misc.IOUtils.readFully 的运行速度会比其他所有方法快两倍? - sissonb
ByteArrayOutputStream.toByteArray() 会创建数据的一个新副本,这是不必要的。有没有避免这种情况的方法? - Mingjiang Shi

5

3
好的,我会尽力进行翻译:嘿,我不想为了一个函数导入一个库。 - sissonb
1
很可能随着时间的推移,您将使用该库中的更多功能,有什么害处呢?反正它是开源的。如果您不想要整个东西,可以阅读源代码并查看它们如何实现。 - bmargulies
1
据我所知,那只是 Apache 的一个旧版本副本。如果您在 IBM JVM 上运行,它不一定会存在。 - bmargulies
这很奇怪。我进行了更彻底的基准测试,发现sun.misc.IOUtils.readFully的运行速度是其他所有方法的两倍。 - sissonb
1
@sissonb,你能否提供一个理由、参考或引用来支持你所说的Sun库“不值得信任”的说法?也许你是对的,但如果没有证据,这就是一个毫无帮助的陈述。 - Chris Hatton
显示剩余4条评论

1
为什么?这段代码与read(byte[])完全等价,除了它在整个数据上进行了两个额外的复制步骤。你不需要这些。一个简单的read(byte[])会快几倍。
同时,使用available()也是无效的。你需要整个响应,而不仅仅是可以无阻塞读取的部分。你需要循环。

谢谢,我现在正在进行这些修复。几分钟后我会展示差异。 - sissonb
一个简单的read()并不能保证(除非这个ChannelBufferInputStream实现带来了这个保证)整个流被读取。也许这不是你真正想要的,但你需要一个读取循环直到返回-1。 - JB Nizet
@JBNizet 同意,澄清。 - user207421

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