我有一个用于下载文件的Web界面。当请求进入时,我的glassfish服务器从Web服务流式传输文件,然后将内容写入输出流。我的代码工作正常,除非文件大小变得非常大(例如超过200 MB),它在浏览器中挂起显示0%已下载并且永远不会下载文件。
当我在while循环中移动flush()方法时,它也适用于大文件。我不确定将flush()放入循环是否有问题。不知道这件事实际上是如何运作的。我的代码如下:
HttpURLConnection conn = (HttpURLConnection) downloadUri.toURL().openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("GET");
conn.setRequestProperty("Content-Type", "application/pdf");
if (conn.getResponseCode() == 200) {
ServletOutputStream output;
try (InputStream inputStream = conn.getInputStream()) {
HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
response.setContentType("application/octet-stream");
response.setHeader("Content-Length", conn.getHeaderField("Content-Length"));
response.setHeader("Content-Disposition", "attachment; filename=\"" + abbr + ".pdf\"");
output = response.getOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
}
output.flush();
output.close();
有什么想法吗?感谢您对此进行调查。
答案 0 :(得分:5)
flush()方法指示流实际将输出发送到流管道。
出于各种性能原因,各种流实现可以缓存输出而不是立即写入底层流。
例如,从性能的角度来看,在磁盘上保存IO操作是很昂贵的。
在刷新流时没有问题,如果不是为了表演,在这种情况下是你想要的:流似乎被卡住直到你冲洗它,所以你希望它实际上发送东西到客户端。 / p>
也许你可以玩大小超过1024的缓冲区大小来看看哪种更适合。
编辑:
在循环中刷新循环或不循环的问题相对无关紧要。
您可以随时调用flush,因为它将调用底层操作系统流,无论是性能损坏还是取决于具体情况。
例如,你可以估计流中缓冲文件的200MB内存比IO操作更重要,性能也更高。
或者更简单地看待用户体验看到实际下载的文件比您可能遇到的最终性能影响更重要,如果您设法测量它。
如上所述,缓冲区越大,循环问题越少。假设,作为一个极端的例子,你的缓冲区是100兆字节,那么一个80兆字节的文件将只获得一个刷新,无论如何它将在请求结束时得到。
拥有1k的缓冲区可能太小,4k更好,16k就好了,这是IO调用和RAM消耗之间的权衡。
流应该自己做正确的工作,如果你看到200MB的文件被完全缓存,除非你调用flush,那么显然流可能是优化性能但是给用户带来不好的体验,所以显然你需要它在循环中。
答案 1 :(得分:2)
正如大卫所说,冲洗迫使下载完成,而不是将所有未写入的数据保存在内存中。
在循环中刷新本身并不是问题,但您可能不希望每次循环运行时都要刷新。请确保在刷新之前不要写太多(如200MB)数据。
编辑: jtahlborn提出了一个很好的观点。 BufferedWriter
会自动刷新。这里的另一个答案更好,但对于那些好奇的人,this is what goes on when you write!
这是public void write(char, int, int)
BufferedWriter
的来源。您可以在第182行看到,它会在达到缓冲区大小后自动刷新(default 8,192)