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

我试图将许多xml文件合并为一个。 我已经在DOM中成功完成了这项工作,但此解决方案仅限于几个文件。 当我在多个文件> 1000上运行它时,我得到一个java.lang.OutOfMemoryError。

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

档案1:

 ....  

文件2:

  ......  

档案n:

  ....  

结果:输出:

   ....   ....   ....   

这是我目前的实施:

  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进行了很多实验,但我似乎总是遗漏一些东西。 任何帮助将受到高度赞赏

您也可以考虑使用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似乎搞乱了空标签,将更改为

只是在没有任何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("\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("\n".getBytes()); // specify encoding too buff.flip(); output.write(buff); } catch (IOException e) { e.printStackTrace(); } 

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

Dom确实消耗了大量内存。 你有,imho,以下的替代品。

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

尝试使用Stax,而不是尝试自己,但它是一种更容易实现和使用类固醇的萨克斯,因为与仅接收您无法控制的萨克斯事件相反,您实际上“请求源”为您传输所需的元素,所以它适合在dom和sax之间的中间,具有类似于sax的内存占用,但是更友好的范例。

Sax,stax,dom都很重要,如果你想正确保存,声明等…名称空间和其他XML奇怪。

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

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

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

我在考虑这样的事情:

 String rootContent = document.substring(document.indexOf(""), document.lastIndexOf("")+7); 

然后避免太多的记忆圆满。 例如,在使用BufferedWritter进行每次xml提取后,在主文件中写入。 为了获得更好的性能,您还可以使用java.nio 。

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

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