节点到字符串 - Java - 大文件
Posted
技术标签:
【中文标题】节点到字符串 - Java - 大文件【英文标题】:Node to String - Java - Large files 【发布时间】:2021-12-17 00:08:27 【问题描述】:我想更改我在 Java 中的一个函数来处理大文件。我已经阅读了有关SAX
、StAX
的信息,但我无法理解它们是如何工作的以及哪种解决方案是最好的。
我目前使用的功能是以下一个:
public static String nodeToString(Node node) throws TransformerFactoryConfigurationError, TransformerException
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
Source source = new DOMSource(node);
StringWriter sw = new StringWriter();
StreamResult result = new StreamResult(sw);
transformer.transform(source, result);
return sw.toString();
编辑:输入参数不能更改(Node node
),返回参数必须是字符串。
我想要一个功能相同但使用 SAX、Stax... 的函数来处理大文件。
编辑2:
我在函数中收到的“节点”(org.w3c.dom.Node)的结构是:
<test>
<test1>
<test2>JVBERi0xLjQKJcOk (This is a base 64 file. The size of this file can be 5MB, 20MB, 400MB. The maximum size is 400MB</test2>
<test3>PDF</test3>
</test1>
<test4>
<test5>Test description</test5>
</test4>
如您所见,“test2”节点保存一个 Base 64 格式的文件。该节点可以保存一个 5MB、20MB、100MB、... 最多 400MB 的文件。
抛出的OOM异常是下面这行:
transformer.transform(source, result);
【问题讨论】:
您好。请考虑添加确切的问题。您想在这里实现什么目标? 刚刚编辑了添加我想要实现的问题的问题。谢谢谢尔盖。 您是在询问解析 XML 还是写出 XML?如果您正在解析 XML,SAX API 很有用 - 但是您已经在内存中获得了Node node
并且您的代码正在转换回 XML。使用 FileWriter 代替 StringWriter 以避免将 XML 的内存占用添加到您的进程中。
@DuncG 我必须做函数的工作。它需要一个节点并返回一个字符串。 OOM 发生在“transformer.transorm(source, result)”中。如果我使用 FileWriter,我是否必须将文件保存在磁盘上然后读取它?非常感谢
问题中没有提到OOM!添加有关失败的 XML 大小的详细信息,并尝试预先调整 StringWriter(estimatedSize)
的大小。你应该让接收者从一个流而不是字符串中读取。
【参考方案1】:
任何依赖于从内存中的另一个 400MB 数据结构实例化 400MB 字符串的代码都是脆弱的 - 我的建议是不要这样做。而是重新设计您的 API,使 String
不是中间数据格式,并且不实例化 400MB 的数据。考虑其中之一:
-
让调用者传递
OutputStream
或Writer
- 这意味着他们可以将XML 直接发送到他们选择的目的地。
保存到文件或数据库以供他们检索。
保存到文件,并使用Files.readString(tempfile)
从文件中读取
如果您继续使用您的代码,您需要更改 VM 设置 -Xmx
为您的进程提供更多内存。但是多少钱?
这两行都在制作约 400MB 数据结构的副本,因此您至少需要大 800MB:
StringWriter sw = new StringWriter(); // Copy of node as XML text
return sw.toString(); // Copy of sw as XML text
但是考虑这个测试程序演示StringWriter()
在附加字符时使用的内存:
private static void test(int size, StringWriter sw)
TreeSet<Integer> sizes = new TreeSet<>();
int capacity = sw.getBuffer().capacity();
for (int i = 0; i < size; i++)
sw.append('X');
sizes.add(sw.getBuffer().capacity());
System.out.println("StringWriter("+capacity+") => sizes: "+sizes);
调用方式:
test(typicalSize, new StringWriter());
test(typicalSize, new StringWriter(typicalSize));
打印:
StringWriter(16) => sizes: [16, 34, 70, 142, 286, 574, 1150, 2302, 4606, 9214, 18430, 36862, 73726, 147454, 294910, 589822, 1179646, 2359294, 4718590, 9437182, 18874366, 37748734, 75497470, 150994942, 301989886, 603979774]
StringWriter(419430400) => sizes: [419430400]
这表明,如果您不预先设置 StringWriter 的大小,内部缓冲区将多次重新分配到 300MB,然后在 sw.toString()
制作的 400MB 副本之前重新分配 600MB。因此,可能需要 > 1.4GB,如果垃圾收集器没有跟上您的分配,请不要惊讶地发现停止 OOM 需要超过 2GB 的额外内存。
注意以上所有估计均假设为紧凑字符串。如果您使用的是旧版 JDK 或 XML 中有多字节字符集,则内存使用量为 char
而不是 byte
。
【讨论】:
以上是关于节点到字符串 - Java - 大文件的主要内容,如果未能解决你的问题,请参考以下文章