我有一个 Web 应用程序,我想在不出现 OOM 的情况下返回包含大量记录的 XLSX。我想将每个页面写入 OutputStream 并在循环中刷新。问题是我遇到了异常。
java.io.IOException: Stream closed
at java.base/java.io.BufferedWriter.ensureOpen(BufferedWriter.java:132) ~[na:na]
at java.base/java.io.BufferedWriter.implWrite(BufferedWriter.java:325) ~[na:na]
at java.base/java.io.BufferedWriter.write(BufferedWriter.java:313) ~[na:na]
at java.base/java.io.Writer.write(Writer.java:278) ~[na:na]
是否可以逐页将数据写入操作系统,或者唯一可能的方法是将所有内容加载到内存中?
我的代码是:
public void export(final OutputStream os) {
try (SXSSFWorkbook workbook = new SXSSFWorkbook(-1)) {
final SXSSFSheet sheet = workbook.createSheet();
for (int i = 0; i < 100; i++) {
final SXSSFRow row = sheet.createRow(i);
final SXSSFCell cell = row.createCell(0, CellType.STRING);
cell.setCellValue("Test");
try {
workbook.writeAvoidingTempFiles(os);
os.flush();
} catch (IOException e) {
log.info("Error", e);
}
}
workbook.dispose();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public void export(final HttpServletResponse httpServletResponse) throws IOException {
try (TestBook workbook = new TestBook()) {
final SXSSFSheet sheet = workbook.createSheet();
for (int i = 0; i < 100; i++) {
final SXSSFRow row = sheet.createRow(i);
final SXSSFCell cell = row.createCell(0, CellType.STRING);
cell.setCellValue("Test");
workbook.write(httpServletResponse.getOutputStream());
httpServletResponse.flushBuffer();
}
}
}
控制器代码:
@GetMapping(produces = MediaType.APPLICATION_OCTET_STREAM_VALUE, path = "/1")
public ResponseEntity<StreamingResponseBody> download1() {
return ResponseEntity .ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "inline;filename=\"" +
"test" +
".xlsx\"")
.body(exportService::export);
}
@GetMapping(produces = MediaType.APPLICATION_OCTET_STREAM_VALUE, path = "/2")
public void download2(final HttpServletResponse httpServletResponse) throws IOException {
httpServletResponse.setHeader("Content-Type", MediaType.APPLICATION_OCTET_STREAM_VALUE);
httpServletResponse.setHeader(HttpHeaders.CONTENT_DISPOSITION, "inline;filename=\"" +
"test2" +
".xlsx\"");
exportService.export(httpServletResponse);
}
static class TestBook extends SXSSFWorkbook {
public TestBook() {
super(-1);
}
}
原写方法如下:
public void write(OutputStream stream) throws IOException {
flushSheets();
//Save the template
File tmplFile = TempFile.createTempFile("poi-sxssf-template", ".xlsx");
boolean deleted;
try {
try (FileOutputStream os = new FileOutputStream(tmplFile)) {
_wb.write(os);
}
//Substitute the template entries with the generated sheet data files
try (
ZipSecureFile zf = new ZipSecureFile(tmplFile);
ZipFileZipEntrySource source = new ZipFileZipEntrySource(zf)
) {
injectData(source, stream);
}
} finally {
deleted = tmplFile.delete();
}
if (!deleted) {
throw new IOException("Could not delete temporary file after processing: " + tmplFile);
}
}
您的工作簿是
SXSSFWorkbook
的子类。对于这种类,您不应该在具有相同输出流的循环中调用 write()
,因为,正如您从源代码中看到的,它将流传递给 injectData
,后者将流包装到 ZipOutputStream 中,写入它,然后关闭 ZipOutputStream,这会导致底层流(your流)也被关闭。
循环的第二次迭代然后使用已经关闭的输出流调用
write()
。
您应该在 for 循环之外调用
write()
并在最后调用它。