如何获取InputStream的大小?

5
我有一个从ProcessBuilder获得的InputStream,它实际上读取的是stdout流。问题:我如何知道这个内存中的InputStream的大小,以便将其写入HttpResponse HTTP标头?
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);

OutputStream out = response.getOutputStream();
int bytes;
while ((bytes = br.read()) != -1) {
    out.write(bytes);
}

//how can I know the size of the inmemory stream/file written?
//response.setContentLength((int) pdfFile.length());

4
只有读完全部内容才会知道结果。输出可能是无限的。 - Peter Lawrey
你最好将其作为流返回给浏览器。 - Peter Lawrey
请参考此答案: https://dev59.com/H2bWa4cB1Zd3GeqPZ8HP - Jitin Kodian
等等,什么让你确定这个内容是文本?而且,你甚至没有在阅读器上指定编码(编辑:从评论中看来,这不是文本)。 - fge
只需使用固定大小的缓冲区,在循环中读取和写入即可。 - Abdelhak
显示剩余2条评论
5个回答

7

输入流没有大小的概念。考虑一个从不退出的程序或一个永远不停止发送数据的套接字对等方。您不需要知道将其写入HttpResponse头中。 Content-length会自动为您管理。


@BalusC 为什么最好使用临时文件? - user207421
@BalusC “内存友好”是什么意思?根据您的评论,内存使用量不超过“缓冲区”。仅使用临时文件会增加延迟:它并不能真正节省内存。 - user207421
@BalusC,你还没有回答我的问题。 - user207421
@BalusC 但他不需要设置内容长度。HttpResponse会为他完成这项工作,如果必要的话还会进行分块。 - user207421
@BalusC 增加延迟只是为了报告进度并不是一个好主意。真正的进展是正在执行的Process的进展,而这仍然对客户端隐藏。 - user207421
显示剩余2条评论

2

如果您真的想设置内容长度头,则需要在写入响应OutputStream之前读取整个流。

ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int count;
while ((count = in.read(bytes)) > 0) {
    out.write(bytes, 0, count);
}
response.setContentLength(out.size();
out.writeTo(response.getOutputStream());

注意:使用这种方法,您现在已经将整个流读入内存中,这将影响可用内存,并且可能无法很好地扩展。

1
什么?不是这样的吗?在写入输出流之前,标题(包括内容长度)已经发送。如果您向输出流写入单个字节,则无法在该点设置标头。因此,流将被发送而没有内容长度标头,这意味着客户端在接收流时无法显示完成百分比。 - lance-java
1
绝对胡说八道。Http协议是先头部,然后再是响应体。当然,你可以在不设置内容长度标头的情况下写入响应OutputStream。但如果你设置了,那就会有好处。或者你的过滤器链中可能已经有一个过滤器为你做了这件事?例如gzip过滤器。 - lance-java
大多数JavaWeb服务器会缓冲ServletOutputStreams直到达到一定长度。如果在缓冲区长度达到之前关闭流,则会写入内容长度标头和缓冲字节。如果写入更多字节,则可能使用分块编码或更可能是需要在服务后关闭连接的不确定体。应该避免这种情况。 - eckes
例如,对于Tomcat,它在doGet()中有文档记录,链接如下:https://tomcat.apache.org/tomcat-8.0-doc/servletapi/javax/servlet/http/HttpServlet.html#doGet(javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse) - eckes
1
如果整个响应适合响应缓冲区,则内容长度将自动设置。我改正了。 - lance-java
显示剩余5条评论

2

试试这个

    InputStream is = process.getInputStream();
    ByteArrayOutputStream os = new ByteArrayOutputStream();
    int b;
    while ((b = is.read()) != -1)
        os.write(b);
    response.setContentLength(os.size());
    response.getOutputStream().write(os.toByteArray());

在Java JDK中从来不需要设置content-length,而在这里使用ByteArrayOutputStream只是浪费时间和空间。并且这并没有回答问题。 - user207421
@user207421 那并不是真的。我需要自己处理内容长度,这正是我所需要的。 - Lerk
@Lerk 你认为你为什么需要那个? - user207421
@user207421 我有一个需要这个的库。但是两年后我不记得是哪一个了,抱歉:( - Lerk

1
import org.apache.commons.io.IOUtils;

   byte[] bytes = IOUtils.toByteArray(inputStream);
   log.message("bytes .lenght "+bytes.length);

            if (bytes.length > 400000)
//some byte range limit`enter code  can add any byte range
              {
                throw new Exception("File Size is larger than 40 MB ..");
              }

0

InputStream 本质上没有大小限制。它可能会不停地传输字节,或者生产者在没有警告的情况下结束流。

如果你必须查找长度,那么你必须读取到末尾,计算字节数,并在完成时报告长度。

你正在担心 HTTP 的 Content-length 头,你有道理。事实上,HTTP 的原始版本并没有为大型动态生成的内容设计。该协议本质上期望您在开始编写内容之前就知道内容的大小 - 然而,如果它是(例如)进行中的聊天或视频摄像机的输出,那怎么可能呢?

解决方案是使用 HTTP 的 chunked transfer encoding。在这里,您不设置 Content-Length 头。您设置 Transfer-Encoding: chunked,然后将内容编写为块,每个块都有一个大小标头。

HTTP RFC 对此有详细说明,或者 https://en.wikipedia.org/wiki/Chunked_transfer_encoding 更加友好些。

然而,大多数HTTP APIs都会对您隐藏这些细节。除非您正在从头开始开发Web库(也许是出于学术原因),否则您不应该考虑Content-LengthTransfer-Encoding


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