首页 > 解决方案 > 如何避免使用 toString().getBytes("UTF-8") 来避免 OOM 错误?有没有更好的方法从 StringWriter 转换为 byte[]?

问题描述

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

下面是代码片段

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

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

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

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

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

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

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

标签: javaarraysjvmout-of-memory

解决方案


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

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

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

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

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
    }
});

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


推荐阅读