Вопрос по xml, performance, merge, java – Как объединить> 1000 XML-файлов в один, используя Java

8

Я пытаюсь объединить много XML-файлов в один. Я успешно сделал это в DOM, но это решение ограничено несколькими файлами. Когда я запускаю его на нескольких файлах & gt; 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 в памяти? Вам нужно больше, чем простая конкатенация строк в этом случае? Yuval Adam
Записать в файл потока. Doomsknight
Почему бы не поместить несколько файлов XML в один архив? Это заканчивается как один файл. Сделайте его несжатым, если важна скорость чтения / записи, и сжатым, если важнее размер файла или пропускная способность. Andrew Thompson
простая конкатенация сохранит объявление XML, если каждый отдельный файл XML будет объединен. Бит действительно в принципе я ищу простую конкатенацию XML-файлов. Andra

Ваш Ответ

6   ответов
1

чтение содержимого файла и создание подстроки проще и достаточно.

Я думаю о чем-то таком:

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

Тогда, чтобы избежать большого потребления памяти. Запись в основной файл после каждого извлечения XML сBufferedWritter например. Для лучшей производительности вы также можете использоватьjava.nio.

2

imho, следующие альтернативы.

Лучше всего использовать SAX. При использовании саксофона используется только очень небольшой объем памяти, потому что практически любой элемент перемещается от входа к выходу в любой момент времени, поэтому объем памяти очень низок. Однако, использование саксофона не так просто, потому что по сравнению с DOM это немного нелогично.

Попробуйте Stax, не пробовал сам, но это своего рода саксофон на стероидах, которые легче реализовать и использовать, потому что, в отличие от просто получения событий саксофона, которые вы не контролируете, вы на самом деле "спрашиваете источник" для потоковой передачи вам элементов, которые вы хотите, поэтому он помещается посередине между dom и sax, имеет объем памяти, похожий на sax, но более дружественную парадигму.

Sax, stax, dom важны, если вы хотите правильно сохранить, объявить и т. Д. Пространства имен и другие странности XML.

Однако, если вам нужен быстрый и грязный способ, который, вероятно, также будет соответствовать пространству имен, используйте простые старые строки и средства записи.

Начните выводить в FileWriter объявление и корневой элемент вашего & quot; большого & quot; документ. Затем загрузите, используя dom, если хотите, каждый отдельный файл. Выберите элементы, которые вы хотите добавить в & quot; большой & quot; файл, сериализовать их обратно в строку и отправить в писатель. писатель будет записывать на диск без использования огромного количества памяти, и dom будет загружать только один документ за одну итерацию. Если у вас также нет очень больших файлов на стороне ввода или вы планируете запускать их на мобильном телефоне, у вас не должно быть проблем с памятью. Если dom сериализует его правильно, он должен сохранять объявления пространства имен и тому подобное, и код будет просто на несколько строк больше, чем тот, который вы опубликовали.

2

какие-либо специальные операции с вашими тегами, я бы просто использовал InputStream и прочитал все файлы. Если вам нужно выполнить некоторые операции, используйте SAX.

10

который будет делать то, что вы хотите:

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>.

3

так как он, по-видимому, не требует какого-либо фактического разбора 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();
        }
1

что вы делаете правильно. Единственный способ масштабировать его до действительно большого количества файлов - это использовать текстовый подход с потоковой передачей, чтобы вы никогда не хранили все это в памяти. Но эй! Хорошие новости. Память дешева в наши дни, и 64-битные JVM все в моде, так что, возможно, все, что вам нужно, это увеличить размер кучи. Попробуйте перезапустить вашу программу с опцией -Xms1g JVM (выделяет начальный размер кучи в 1 Гб).

Я также склонен использоватьXOM для всех моих требований DOM. Попробуй. Гораздо эффективнее. Не знаю наверняка о требованиях к памяти, но, по моему опыту, она на несколько порядков быстрее.

Похожие вопросы