如何合并>;使用Java将1000个xml文件合并为一个

如何合并>;使用Java将1000个xml文件合并为一个,java,xml,performance,merge,out-of-memory,Java,Xml,Performance,Merge,Out Of Memory,我正在尝试将多个xml文件合并为一个。我已经在DOM中成功地做到了这一点,但是这个解决方案仅限于几个文件。当我在多个大于1000的文件上运行它时,我得到一个java.lang.OutOfMemoryError 我想要实现的是我有以下文件的地方 文件1: <root> .... </root> .... 文件2: <root> ...... </root> ...... 文件n: <root> .... </root&g

我正在尝试将多个xml文件合并为一个。我已经在DOM中成功地做到了这一点,但是这个解决方案仅限于几个文件。当我在多个大于1000的文件上运行它时,我得到一个java.lang.OutOfMemoryError

我想要实现的是我有以下文件的地方

文件1:

<root>
....
</root>

....
文件2:

<root>
......
</root>

......
文件n:

<root>
....
</root>

....
导致: 输出:


....
....
....
这是我当前的实现:

    DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
    Document doc = docBuilder.newDocument();
    Element rootSetElement = doc.createElement("rootSet");
    Node rootSetNode = doc.appendChild(rootSetElement);
    Element creationElement = doc.createElement("creationDate");
    rootSetNode.appendChild(creationElement);
    creationElement.setTextContent(dateString); 
    File dir = new File("/tmp/rootFiles");
    String[] files = dir.list();
    if (files == null) {
        System.out.println("No roots to merge!");
    } else {
        Document rootDocument;
            for (int i=0; i<files.length; i++) {
                       File filename = new File(dir+"/"+files[i]);        
               rootDocument = docBuilder.parse(filename);
               Node tempDoc = doc.importNode((Node) Document.getElementsByTagName("root").item(0), true);
               rootSetNode.appendChild(tempDoc);
        }
    }   
DocumentBuilderFactory docFactory=DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder=docFactory.newDocumentBuilder();
Document doc=docBuilder.newDocument();
元素rootSetElement=doc.createElement(“根集”);
节点rootSetNode=doc.appendChild(rootSetElement);
元素creationElement=doc.createElement(“creationDate”);
appendChild(creationElement);
creationElement.setTextContent(日期字符串);
File dir=新文件(“/tmp/rootFiles”);
String[]files=dir.list();
if(files==null){
System.out.println(“没有要合并的根!”);
}否则{
文档根文档;

对于(int i=0;iDOM需要将整个文档保存在内存中。如果不需要对标记执行任何特殊操作,我只需使用InputStream并读取所有文件。如果需要执行某些操作,则使用SAX。

对于此类工作,我建议不要使用DOM,读取文件内容并生成子字符串更简单够了

我在想这样的事情:

String rootContent = document.substring(document.indexOf("<root>"), document.lastIndexOf("</root>")+7);
String rootContent=document.substring(document.indexOf(“”),document.lastIndexOf(“”+7);

然后,为了避免太多的内存消耗。例如,每次提取xml后,使用
缓冲写入器写入主文件。为了获得更好的性能,您也可以使用。

只需不进行任何xml解析即可,因为它似乎不需要对xml进行任何实际解析

为了提高效率,请执行以下操作:

File dir = new File("/tmp/rootFiles");
String[] files = dir.list();
if (files == null) {
    System.out.println("No roots to merge!");
} else {
        try (FileChannel output = new FileOutputStream("output").getChannel()) {
            ByteBuffer buff = ByteBuffer.allocate(32);
            buff.put("<rootSet>\n".getBytes()); // specify encoding too
            buff.flip();
            output.write(buff);
            buff.clear();
            for (String file : files) {
                try (FileChannel in = new FileInputStream(new File(dir, file).getChannel()) {
                    in.transferTo(0, 1 << 24, output);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            buff.put("</rootSet>\n".getBytes()); // specify encoding too
            buff.flip();
            output.write(buff);
        } catch (IOException e) {
            e.printStackTrace();
        }
File dir=新文件(“/tmp/rootFiles”);
String[]files=dir.list();
if(files==null){
System.out.println(“没有要合并的根!”);
}否则{
try(FileChannel output=newfileoutputstream(“output”).getChannel(){
ByteBuffer buff=ByteBuffer.allocate(32);
buff.put(“\n”.getBytes());//也指定编码
buff.flip();
输出写入(buff);
buff.clear();
用于(字符串文件:文件){
try(FileChannel in=newfileinputstream(新文件(dir,File).getChannel()){

在.transferTo(0,1Dom中确实消耗了大量内存

最好的方法是使用SAX。使用SAX,只使用了非常小的内存量,因为基本上几乎只有一个元素在任何给定的时间从输入移动到输出,所以内存占用非常低。但是,使用SAX并不是那么简单,因为与dom相比,它有点违反直觉

尝试STAX,而不是尝试自己,但它是类固醇上更容易实现和使用的一种SAX,因为与接收不控制的SAX事件相反,您实际上是“请求源”来向您流出您想要的元素,因此它适合于DOM和SAX之间,具有类似SAX的内存占用,但更友好的范例。

如果您想正确地保留、声明等名称空间和其他XML奇怪之处,Sax、stax和dom都很重要

然而,如果您只需要一种快速而肮脏的方法,这可能也会与命名空间兼容,那么请使用普通的旧字符串和编写器


开始将声明和“大”文档的根元素输出到FileWriter。然后,如果愿意,使用dom加载每个文件。选择要在“大”文档中结束的元素文件,将其序列化为字符串,然后将其发送给编写器。编写器将刷新到磁盘而不使用大量内存,dom每次迭代只加载一个文档。除非输入端也有非常大的文件,或者计划在手机上运行,否则您不应该有太多内存问题。如果dom将其序列化为坦白地说,它应该保留名称空间声明等,代码将比您发布的代码多出一堆行。

我认为您所做的是有效的。使其扩展到真正大量文件的唯一方法是使用基于文本的流式处理方法,因此您永远不会将整个内容保留在内存中。但是,嘿!很好新闻。现在内存很便宜,64位JVM非常流行,所以您可能只需要增加堆大小。尝试使用-Xms1g JVM选项(分配1Gb初始堆大小)重新运行您的程序


我也倾向于使用我所有的DOM要求。给它一个GO。效率更高。对内存要求不确定,但我的经验中它的数量级更快。

你也可以考虑使用StAX。下面是你想要的代码:

import java.io.File;
import java.io.FileWriter;
import java.io.Writer;

import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.stream.StreamSource;

public class XMLConcat {
    public static void main(String[] args) throws Throwable {
        File dir = new File("/tmp/rootFiles");
        File[] rootFiles = dir.listFiles();

        Writer outputWriter = new FileWriter("/tmp/mergedFile.xml");
        XMLOutputFactory xmlOutFactory = XMLOutputFactory.newFactory();
        XMLEventWriter xmlEventWriter = xmlOutFactory.createXMLEventWriter(outputWriter);
        XMLEventFactory xmlEventFactory = XMLEventFactory.newFactory();

        xmlEventWriter.add(xmlEventFactory.createStartDocument());
        xmlEventWriter.add(xmlEventFactory.createStartElement("", null, "rootSet"));

        XMLInputFactory xmlInFactory = XMLInputFactory.newFactory();
        for (File rootFile : rootFiles) {
            XMLEventReader xmlEventReader = xmlInFactory.createXMLEventReader(new StreamSource(rootFile));
            XMLEvent event = xmlEventReader.nextEvent();
            // Skip ahead in the input to the opening document element
            while (event.getEventType() != XMLEvent.START_ELEMENT) {
                event = xmlEventReader.nextEvent();
            }

            do {
                xmlEventWriter.add(event);
                event = xmlEventReader.nextEvent();
            } while (event.getEventType() != XMLEvent.END_DOCUMENT);
            xmlEventReader.close();
        }

        xmlEventWriter.add(xmlEventFactory.createEndElement("", null, "rootSet"));
        xmlEventWriter.add(xmlEventFactory.createEndDocument());

        xmlEventWriter.close();
        outputWriter.close();
    }
}

一个小警告是,这个API似乎弄乱了空标记,将
更改为

是否有任何理由需要将DOM保留在内存中?在这种情况下,您需要的不仅仅是简单的字符串连接吗?如果合并每个单独的xml文件,简单的连接将保留xml声明原则上,我正在寻找xml文件的简单连接。为什么不将多个xml文件放在一个存档中?它最终会成为一个文件。如果读/写速度很重要,请将其解压缩,如果文件大小或带宽更重要,请将其压缩。
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;

import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.stream.StreamSource;

public class XMLConcat {
    public static void main(String[] args) throws Throwable {
        File dir = new File("/tmp/rootFiles");
        File[] rootFiles = dir.listFiles();

        Writer outputWriter = new FileWriter("/tmp/mergedFile.xml");
        XMLOutputFactory xmlOutFactory = XMLOutputFactory.newFactory();
        XMLEventWriter xmlEventWriter = xmlOutFactory.createXMLEventWriter(outputWriter);
        XMLEventFactory xmlEventFactory = XMLEventFactory.newFactory();

        xmlEventWriter.add(xmlEventFactory.createStartDocument());
        xmlEventWriter.add(xmlEventFactory.createStartElement("", null, "rootSet"));

        XMLInputFactory xmlInFactory = XMLInputFactory.newFactory();
        for (File rootFile : rootFiles) {
            XMLEventReader xmlEventReader = xmlInFactory.createXMLEventReader(new StreamSource(rootFile));
            XMLEvent event = xmlEventReader.nextEvent();
            // Skip ahead in the input to the opening document element
            while (event.getEventType() != XMLEvent.START_ELEMENT) {
                event = xmlEventReader.nextEvent();
            }

            do {
                xmlEventWriter.add(event);
                event = xmlEventReader.nextEvent();
            } while (event.getEventType() != XMLEvent.END_DOCUMENT);
            xmlEventReader.close();
        }

        xmlEventWriter.add(xmlEventFactory.createEndElement("", null, "rootSet"));
        xmlEventWriter.add(xmlEventFactory.createEndDocument());

        xmlEventWriter.close();
        outputWriter.close();
    }
}