【问题标题】:how to avoid use of toString().getBytes("UTF-8") to avoid OOM error? Is there any better approach to convert to byte[] from StringWriter?如何避免使用 toString().getBytes("UTF-8") 来避免 OOM 错误?有没有更好的方法从 StringWriter 转换为 byte[] ?
【发布时间】:2019-01-14 18:15:27
【问题描述】:

我有一个生成报告的报告网络应用程序。应用程序从数据库中获取数据并将数据存储到StringWriter 对象中。我必须以字节数组格式获取这些数据以创建 csv 文件并将其发送到浏览器。

下面是代码sn-p

 return new FileTransfer(fileName, reportType.getMimeType(),
                    new ByteArrayInputStream(generateCSV(reportType, grid, new DataList(), params).toString().getBytes("UTF-8")));

其中generateCSV 返回一个StringWriter 对象,然后将其转换为字节数组,我调用toString,然后调用getBytes() 方法。下面是generateCSV 方法的样子

StringWriter generateCSV(ReportType reportType, GridConfig grid, DataList dataList, String params) {......}

问题是,当我的报告有大量记录(超过 100 万条)时,getBytes() 方法会失败并显示

java.lang.OutOfMemoryError: 请求的数组大小超过 VM 限制

转换为字符串对象时的整个报表数据包含大量字符(数十亿)。 .getBytes("UTF-8") 方法将其转换为数组,每个数组元素为一个字符。对于 100 万条记录,该字符超出了 MAX JVM ARRAY 大小限制 (https://plumbr.io/outofmemoryerror/requested-array-size-exceeds-vm-limit)。

现在如何避免使用toString().getBytes("UTF-8") 来避免OOM 错误?有没有更好的方法从StringWriter 转换为字节数组?

【问题讨论】:

  • generateCSV方法是否直接将CSV生成为StringWriter
  • 什么是FileTransfer?它真的要求ByteArrayInputStream吗?或者它是否支持Reader(那么你可以使用StringReader)或至少是任意的InputStreams(在这种情况下,Apache Commons 有一个ReaderInputStream)?
  • 考虑使用临时文件和输入/输出流(或阅读器),而不是将所有内容都保存在内存中。
  • “我必须以字节数组格式获取这些数据以创建 csv 文件并将其发送到浏览器”。我怀疑这种说法是否属实。如果你正在写出文本,你想写成一个正确编码的字节流。
  • Hi @Lino 是的 generateCSV 方法将报告数据生成到 StringWriter 对象中。

标签: java arrays jvm out-of-memory


【解决方案1】:

收到generateCSV 的结果为StringWriter 很奇怪;首选的解决方案是让方法在生成时写入目标,这样您就不会在内存中拥有全部内容。

在任何一种情况下,您都应该求助于FileTransfer(String, String mimeType, OutputStreamLoader) 构造函数,以便在写入实际数据时接收目标OutputStream

当您无法避免中间 StringWriter 时,您至少应该避免在其上调用 toString,因为构造 String 意味着创建整个缓冲区的副本。

所以解决方案可能如下所示:

return new FileTransfer(fileName, reportType.getMimeType(), new OutputStreamLoader() {
    public void close() {}
    public void load(OutputStream out) throws IOException {
        // the best would be to let generateCSV write to out directly
        // otherwise use:
        StringBuffer sb = generateCSV(reportType, grid, new DataList(), params).getBuffer();
        Writer w = new OutputStreamWriter(out, "UTF-8")
        final int bufSize = 8192;
        for(int s = 0, e; s < sb.length(); s = e) {
            e = Math.min(sb.length(), s + bufSize);
            w.write(sb.substring(s, e));
        }
        w.flush(); // let the caller close the OutputStream
    }
});

StringWriter 的替代方案是CharArrayWriter,它有一个writeTo​(Writer out),这样就无需实现手动复制循环,并且可能更有效。但是,如前所述,重构 generateCSV 以直接写入目标会更好。

【讨论】:

    【解决方案2】:

    StringWriter 将其内容保存在内存中。因此,将它用于大文件并不是一个好方法。

    您应该尝试将文件直接分块到 InputStream,中间不使用 StringWriter。 您自己的InputStream 实现如何读取文件并将其即时转换为 csv。

    你能告诉我们 generateCSV 方法吗?

    【讨论】:

    • protected StringWriter generateCSV(ReportType reportType, GridConfig grid, DataList dataList, String params) { Header header = getReportHeaderData(reportType, null); RecordList list = getReportData(reportType, grid, params); dataList.setHEADER(标题); StringWriter out = new StringWriter();字符分隔符 = reportHelper.getListSeparator(); CSVWriter writer = new CSVWriter(out, separator); //其中 CSVWriter 是公共类 CSVWriter extends au.com.bytecode.opencsv.CSVWriter //从数据库中获取报告数据的逻辑 return out; }
    猜你喜欢
    • 1970-01-01
    • 2021-10-10
    • 2021-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-10
    相关资源
    最近更新 更多