You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Java拆分大型XML文件并解决DOM操作内存溢出问题

解决大型XML文件的Java DOM内存溢出问题及拆分方案

兄弟,你遇到的java.lang.OutOfMemoryError: Java heap space完全是DOM解析的“天生缺陷”导致的——DOM会把整个XML文档完整加载到内存里构建树形结构,25GB的文件别说JVM堆内存,就算你把机器所有内存都给JVM也扛不住啊!

要处理这种超大XML,必须换用流式解析方案,比如SAX或者StAX。其中StAX比SAX更灵活,支持双向读写,非常适合这种需要筛选标签+拆分文件的场景。下面直接给你可行的代码方案,思路是逐节点读取XML,只保留你需要的标签,同时每处理一定数量的<page>节点就写入一个新的小文件:

首先,导入必要的包:

import javax.xml.stream.*;
import javax.xml.stream.events.*;
import java.io.*;
import java.util.HashSet;
import java.util.Set;

然后是核心处理类:

public class FrWikiProcessor {
    // 定义需要保留的标签集合,和你原来的筛选逻辑一致
    private static final Set<String> ALLOWED_TAGS = new HashSet<>();
    static {
        ALLOWED_TAGS.add("mediawiki");
        ALLOWED_TAGS.add("page");
        ALLOWED_TAGS.add("revision");
        ALLOWED_TAGS.add("id");
        ALLOWED_TAGS.add("title");
        ALLOWED_TAGS.add("text");
    }

    private final String inputFilePath;
    private final String outputDir;
    private int fileCounter = 1;
    private int pageCounter = 0;
    // 每个小文件包含1000个page节点,你可以根据需求调整这个数值
    private static final int PAGES_PER_FILE = 1000;

    public FrWikiProcessor(String inputFilePath, String outputDir) {
        this.inputFilePath = inputFilePath;
        this.outputDir = outputDir;
        // 自动创建输出目录,避免找不到路径的问题
        new File(outputDir).mkdirs();
    }

    public void processAndSplit() throws XMLStreamException, IOException {
        XMLInputFactory inputFactory = XMLInputFactory.newInstance();
        XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();

        try (FileInputStream fis = new FileInputStream(inputFilePath)) {
            XMLEventReader eventReader = inputFactory.createXMLEventReader(fis);
            XMLEventWriter eventWriter = null;
            boolean insideAllowedTag = false;

            while (eventReader.hasNext()) {
                XMLEvent event = eventReader.nextEvent();

                if (event.isStartElement()) {
                    StartElement startElement = event.asStartElement();
                    String tagName = startElement.getName().getLocalPart();

                    if (ALLOWED_TAGS.contains(tagName)) {
                        insideAllowedTag = true;

                        // 遇到page节点时计数,判断是否需要新建输出文件
                        if ("page".equals(tagName)) {
                            pageCounter++;
                            if (pageCounter % PAGES_PER_FILE == 1) {
                                // 先关闭上一个文件的writer
                                if (eventWriter != null) {
                                    eventWriter.add(outputFactory.createEndElement("", "", "mediawiki"));
                                    eventWriter.add(outputFactory.createEndDocument());
                                    eventWriter.close();
                                }
                                // 创建新的输出文件
                                String outputFilePath = outputDir + "/frwiki-part-" + fileCounter + ".xml";
                                eventWriter = outputFactory.createXMLEventWriter(new FileOutputStream(outputFilePath));
                                // 写入XML声明和根标签开始
                                eventWriter.add(outputFactory.createStartDocument("UTF-8", "1.0"));
                                eventWriter.add(outputFactory.createStartElement("", "", "mediawiki"));
                                fileCounter++;
                            }
                        }

                        // 把允许的开始标签写入输出文件
                        eventWriter.add(event);
                    } else {
                        insideAllowedTag = false;
                    }
                } else if (event.isEndElement()) {
                    EndElement endElement = event.asEndElement();
                    String tagName = endElement.getName().getLocalPart();

                    if (ALLOWED_TAGS.contains(tagName)) {
                        insideAllowedTag = true;
                        // 把允许的结束标签写入输出文件
                        eventWriter.add(event);
                    }
                } else if (insideAllowedTag && (event.isCharacters() || event.isProcessingInstruction())) {
                    // 写入允许标签内的文本内容或处理指令
                    eventWriter.add(event);
                }
            }

            // 处理最后一个文件的收尾工作
            if (eventWriter != null) {
                eventWriter.add(outputFactory.createEndElement("", "", "mediawiki"));
                eventWriter.add(outputFactory.createEndDocument());
                eventWriter.close();
            }
        }
    }

    public static void main(String[] args) {
        try {
            FrWikiProcessor processor = new FrWikiProcessor(
                    "D:\\compressed\\frwiki-latest-pages-articles.xml",
                    "D:\\compressed\\frwiki-split-parts"
            );
            processor.processAndSplit();
            System.out.println("文件处理完成,共生成 " + (processor.fileCounter - 1) + " 个小文件");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

关键说明:

  1. 流式处理逻辑:逐事件读取XML,只处理你需要的标签,全程不会把整个文档加载到内存,彻底解决内存溢出问题。
  2. 自动拆分文件:通过PAGES_PER_FILE控制每个小文件的<page>节点数量,你可以根据期望的小文件大小调整这个值。
  3. 标签筛选:和你原来的需求完全一致,只保留id、title、text等指定标签,其他标签直接忽略。
  4. 资源管理:用try-with-resources自动关闭输入流,避免资源泄漏;每次切换文件时正确关闭旧的writer,保证XML结构完整。

另外提一句,你原来的代码里循环3次删除标签其实没必要——DOM的NodeList是动态的,删除节点后长度会变化,多次循环反而容易出错,流式处理一次遍历就搞定所有筛选工作。

内容的提问来源于stack exchange,提问作者Moharach

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 17:01:49