【问题标题】:How to merge >1000 xml files into one using Java如何使用Java将> 1000个xml文件合并为一个
【发布时间】:2012-06-01 08:27:40
【问题描述】:

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

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

文件 1:

<root>
....
</root>

文件 2:

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

文件 n:

<root>
....
</root>

导致: 输出:

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

这是我当前的实现:

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

我对 xslt、sax 进行了很多实验,但我似乎总是遗漏一些东西。任何帮助将不胜感激

【问题讨论】:

  • 有什么理由需要将 DOM 实际保存在内存中吗?在这种情况下,您需要的不仅仅是简单的字符串连接吗?
  • 如果每个单独的 xml 文件被合并,简单的连接将保留 xml 声明。原则上我确实在寻找一个简单的 xml 文件串联。
  • 为什么不将多个 XML 文件放在一个存档中?它最终成为一个文件。如果读/写速度很重要,则将其解压缩,如果文件大小或带宽更重要,则将其压缩。

标签: java xml performance merge out-of-memory


【解决方案1】:

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

我也倾向于使用XOM 来满足我的所有 DOM 需求。搏一搏。效率更高。不确定内存要求,但根据我的经验,它的速度要快几个数量级。

【讨论】:

    【解决方案2】:

    无需任何 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();
            }
    

    【讨论】:

      【解决方案3】:

      对于这种工作,我建议不要使用 DOM,读取文件内容并制作子字符串更简单也足够了。

      我正在考虑类似的事情:

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

      然后避免过多的内存消耗。例如,在每次提取 xml 后使用 BufferedWritter 写入主文件。为了获得更好的性能,您还可以使用java.nio

      【讨论】:

        【解决方案4】:

        您也可以考虑使用 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 似乎与空标签混淆,将 &lt;foo/&gt; 更改为 &lt;foo&gt;&lt;/foo&gt;

        【讨论】:

          【解决方案5】:

          Dom 确实会消耗大量内存。恕我直言,您有以下选择。

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

          试试 Stax,我自己没试过,但它是一种更容易实现和使用的 sax你想要的,所以它介于 dom 和 sax 之间,具有类似于 sax 的内存占用,但更友好的范例。

          如果您想正确保存、声明等...命名空间和其他 XML 奇怪的东西,Sax、stax、dom 都很重要。

          但是,如果您只需要一种快速而肮脏的方式,并且可能也符合命名空间,请使用普通的旧字符串和编写器。

          开始向 FileWriter 输出“大”文档的声明和根元素。然后根据需要使用 dom 加载每个文件。选择要在“大”文件中结束的元素,将它们序列化回字符串,然后将其发送给编写器。 writer 将在不使用大量内存的情况下刷新到磁盘,并且 dom 每次迭代只会加载一个文档。除非您在输入端也有非常大的文件,或者计划在手机上运行它,否则您不应该有很多内存问题。如果 dom 正确序列化它,它应该保留命名空间声明等,并且代码将比您发布的代码多行。

          【讨论】:

            【解决方案6】:

            DOM 需要将整个文档保存在内存中。如果您不需要对标签进行任何特殊操作,我只需使用 InputStream 并读取所有文件。如果你需要做一些操作,那就使用 SAX。

            【讨论】:

              猜你喜欢
              • 2012-04-24
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-02-06
              • 1970-01-01
              相关资源
              最近更新 更多