Как объединить > 1000 xml-файлов в один с помощью Java
Я пытаюсь объединить много XML-файлов в один. Я успешно сделал это в DOM, но это решение ограничено несколькими файлами. Когда я запускаю его на нескольких файлах >1000, я получаю java.ленг.Исключение 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, но, похоже, мне что-то не хватает. Любая помощь будет высоко оценена
6 ответов
вы также можете рассмотреть возможность использования 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, похоже, возится с пустыми тегами, изменяя <foo/>
на <foo></foo>
.
просто сделайте это без какого-либо 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();
}
DOM должен хранить весь документ в памяти. Если вам не нужно делать какие-либо специальные операции с вашими тегами, я бы просто использовал InputStream и прочитал все файлы. Если вам нужно выполнить некоторые операции, используйте SAX.
Dom действительно потребляет много памяти. У вас есть, имхо, следующие альтернативы.
лучший-использовать Sax. Используя sax, используется только очень небольшой объем памяти, потому что в любой момент времени почти один элемент перемещается от входа к выходу, поэтому объем памяти чрезвычайно низок. Однако использование sax не так просто, потому что по сравнению с dom это немного противоречит интуиции.
попробуйте Stax, не пробовал себя, но это своего рода саксофон на стероидах легче реализуйте и используйте, потому что, в отличие от получения событий sax, которые вы не контролируете, вы фактически "просите источник" передать вам нужные элементы, поэтому он помещается посередине между dom и sax, имеет объем памяти, подобный sax, но более дружественную парадигму.
Sax, stax, dom важны, если вы хотите правильно сохранить, объявить и т. д... пространства имен и другие странности XML.
однако, если вам просто нужен быстрый и грязный способ, который, вероятно, будет пространство имен совместимо также, используйте простые старые строки и записи.
начните выводить на FileWriter объявление и корневой элемент вашего" большого " документа. Затем загрузите, используя dom, если хотите, каждый файл. Выберите элементы, которые вы хотите получить в "большом" файле, сериализуйте их обратно в строку и отправьте в writer. писатель будет сбрасываться на диск без использования огромного объема памяти, а dom будет загружать только один документ на итерацию. Если у вас также нет очень большие файлы на стороне ввода или планируют запустить его на мобильном телефоне, у вас не должно быть много проблем с памятью. Если dom сериализует его правильно, он должен сохранять объявления пространства имен и тому подобное, и код будет просто кучей строк больше, чем тот, который вы опубликовали.
для такого рода работы я предлагаю не использовать DOM, чтение содержимого файла и создание подстроки проще и достаточно.
Я думаю о чем-то вроде этого :
String rootContent = document.substring(document.indexOf("<root>"), document.lastIndexOf("</root>")+7);
затем, чтобы избежать много потребление памяти. Запись в основной файл после каждого извлечения xml с BufferedWritter
например. Для лучшей производительности вы также можете использовать java.НИО!--8-->.
Я думаю, что то, что вы делаете, действительно. Единственный способ сделать его масштабируемым до действительно огромного количества файлов-использовать текстовый подход с потоковой передачей, поэтому вы никогда не держите все это в памяти. Но, эй! Хорошие новости. Память дешева в эти дни, и 64-битные JVMs-это все ярость, поэтому, возможно, все, что вам нужно, это увеличить размер кучи. Попробуйте повторно запустить программу с параметром-Xms1g JVM (выделяет начальный размер кучи 1Gb).
Я также склонен использовать XOM для всех моих DOM требования. Попробуй. Гораздо более эффективный. Не знаю точно о требованиях к памяти, но на порядок быстрее в моем опыте.