将一个InputStream写入HttpServletResponse

27

我有一个InputStream,想要将其写入HttpServletResponse。有一种方法可以实现,但由于使用了byte[],速度太慢了。

InputStream is = getInputStream();
int contentLength = getContentLength();

byte[] data = new byte[contentLength];
is.read(data);

//response here is the HttpServletResponse object
response.setContentLength(contentLength);
response.write(data);

我在想,在速度和效率方面,可能有什么最好的方法可以实现它。

3个回答

53

不要将整个内容完全复制到Java的内存中,而是直接使用分块写入。以下基本示例以10KB的块大小写入。这样,你最终只会使用一致的10KB内存,而不是完整的内容长度。此外,最终用户将更快地获取内容的部分。

response.setContentLength(getContentLength());
byte[] buffer = new byte[10240];

try (
    InputStream input = getInputStream();
    OutputStream output = response.getOutputStream();
) {
    for (int length = 0; (length = input.read(buffer)) > 0;) {
        output.write(buffer, 0, length);
    }
}

为了获得最佳的性能,你可以使用NIO通道和直接分配的ByteBuffer。在自定义工具类例如Utils中创建以下实用程序/辅助方法:

public static long stream(InputStream input, OutputStream output) throws IOException {
    try (
        ReadableByteChannel inputChannel = Channels.newChannel(input);
        WritableByteChannel outputChannel = Channels.newChannel(output);
    ) {
        ByteBuffer buffer = ByteBuffer.allocateDirect(10240);
        long size = 0;

        while (inputChannel.read(buffer) != -1) {
            buffer.flip();
            size += outputChannel.write(buffer);
            buffer.clear();
        }

        return size;
    }
}

然后你可以按照以下方式使用它:

response.setContentLength(getContentLength());
Utils.stream(getInputStream(), response.getOutputStream());

2
当然,许多实用程序包已经定义了这种方法,因此一旦您开始使用Guava... http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/io/ByteStreams.html#copy(java.io.InputStream, java.io.OutputStream) - Michael Brewer-Davis
+1 @BalusC 我们需要设置它的ContentType吗?如果是,那应该是什么? - Roy Lee
1
@Roylee:是的,这是推荐的做法。只需将内容类型设置为内容的类型 :) http://www.freeformatter.com/mime-types-list.html - BalusC
我尝试过这个,为什么我的流会返回这样的错误:org.apache.catalina.connector.ClientAbortException: java.io.IOException: An established connection was aborted by the software in your host machine at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:356) at org.apache.catalina.connector.OutputBuffer.appendByteArray(OutputBuffer.java:778) at org.apache.catalina.connector.OutputBuffer.append(OutputBuffer.java:707) at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:391) - vvavepacket
如何获取输入流的getContentLength()? - Pramod Gaikwad

1
BufferedInputStream in = null;
BufferedOutputStream out = null;
OutputStream os;
os = new BufferedOutputStream(response.getOutputStream());
in = new BufferedInputStream(new FileInputStream(file));
out = new BufferedOutputStream(os);
byte[] buffer = new byte[1024 * 8];
int j = -1;
while ((j = in.read(buffer)) != -1) {
    out.write(buffer, 0, j);
}

如果可能的话,我想避免使用byte[]。 - Sabry Shawally
@MuhammadSabry 没有使用 byte[] 的方法可以读取 InputStream。 - Luiggi Mendoza
byte[]出了什么问题?你需要将数据存储在某个地方:} - MTilsted

0

我认为这非常接近最佳的方式,但我建议进行以下更改。使用固定大小缓冲区(例如20K),然后通过循环执行读/写操作。

对于循环,请执行以下操作

byte[] buffer=new byte[20*1024];
outputStream=response.getOutputStream();
while(true) {
  int readSize=is.read(buffer);
  if(readSize==-1)
    break;
  outputStream.write(buffer,0,readSize);
}

提示:您的程序不一定总是能正常工作,因为读取操作并不总是会填满您提供的整个数组。


你所说的“读取不会填满给定的整个数组”是什么意思? - Sabry Shawally
1
读取操作并不总是填满输入数组。因此,您需要检查读取返回的值,即读取的字节数。(请参见http://docs.oracle.com/javase/6/docs/api/java/io/InputStream.html#read%28byte[]%29) - MTilsted

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