节点到字符串 - Java - 大文件

Posted

技术标签:

【中文标题】节点到字符串 - Java - 大文件【英文标题】:Node to String - Java - Large files 【发布时间】:2021-12-17 00:08:27 【问题描述】:

我想更改我在 Java 中的一个函数来处理大文件。我已经阅读了有关SAXStAX 的信息,但我无法理解它们是如何工作的以及哪种解决方案是最好的。

我目前使用的功能是以下一个:

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 的数据。考虑其中之一:

    让调用者传递OutputStreamWriter - 这意味着他们可以将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 - 大文件的主要内容,如果未能解决你的问题,请参考以下文章

请教Java处理大批量的数据

Java 创建一个大文件

在java中创建XML文件时传递字符串而不是节点

java怎么在xml文件中保存和读取字符串数组

将字符串 XML 片段转换为 Java 中的文档节点

使用 Java 读取文件或流的最强大的方法(以防止 DoS 攻击)