Как изменить огромный файл XML с помощью StAX?

У меня есть огромный XML (~ 2 ГБ), и мне нужно добавить новые элементы и изменить старые. Например, у меня есть:

<books>
    <book>....</book>
    ...
    <book>....</book>
</books>

И хотите получить:

<books>
   <book>
      <index></index>
      ....
   </book>
   ...
   <book>
      <index></index>
      ....
   </book>
</books>

Я использовал следующий код:

XMLInputFactory inFactory = XMLInputFactory.newInstance();
XMLEventReader eventReader = inFactory.createXMLEventReader(new FileInputStream(file));
XMLOutputFactory factory = XMLOutputFactory.newInstance();
XMLStreamWriter writer = factory.createXMLStreamWriter(new FileWriter(file, true));
while (eventReader.hasNext()) {
   XMLEvent event = eventReader.nextEvent();
   if (event.getEventType() == XMLEvent.START_ELEMENT) {
      if (event.asStartElement().getName().toString().equalsIgnoreCase("book")) {
          writer.writeStartElement("index");
          writer.writeEndElement();
       }
    }
}
writer.close();

Но результат был следующим:

<books>
   <book>....</book>
   ....
   <book>....</book>
</books><index></index>

Любые идеи?


person Eugene    schedule 10.05.2013    source источник


Ответы (3)


Попробуй это

    XMLInputFactory inFactory = XMLInputFactory.newInstance();
    XMLEventReader eventReader = inFactory.createXMLEventReader(new FileInputStream("1.xml"));
    XMLOutputFactory factory = XMLOutputFactory.newInstance();
    XMLEventWriter writer = factory.createXMLEventWriter(new FileWriter(file));
    XMLEventFactory eventFactory = XMLEventFactory.newInstance();
    while (eventReader.hasNext()) {
        XMLEvent event = eventReader.nextEvent();
        writer.add(event);
        if (event.getEventType() == XMLEvent.START_ELEMENT) {
            if (event.asStartElement().getName().toString().equalsIgnoreCase("book")) {
                writer.add(eventFactory.createStartElement("", null, "index"));
                writer.add(eventFactory.createEndElement("", null, "index"));
            }
        }
    }
    writer.close();

Заметки

новый FileWriter(file, true) добавляется в конец файла, он вам вряд ли нужен

equalsIgnoreCase("book") - плохая идея, потому что XML чувствителен к регистру.

person Evgeniy Dorofeev    schedule 10.05.2013
comment
К сожалению, этот код не работает. NetBeans выдает ошибку: «Вызвано: javax.xml.stream.XMLStreamException: ParseError at [row, col]:[4,2] Сообщение: Структуры документов XML должны начинаться и заканчиваться в одном и том же объекте. на com.sun.org.apache.xerces.internal.impl.XMLStreamReaderImpl.next(XMLStreamReaderImpl.java:598) на com.sun.xml.internal.stream.XMLEventReaderImpl.nextEvent(XMLEventReaderImpl.java:83) на librarian.controllers .BookCardController.saveToXML(BookCardController.java:140) ... еще 54' И, кроме того, удаляет все содержимое файла... - person Eugene; 10.05.2013
comment
какое исключение? Я проверил это с вашим xml перед публикацией - person Evgeniy Dorofeev; 10.05.2013
comment
Только что попробовал. И снова то же самое исключение: «Вызвано: javax.xml.stream.XMLStreamException: ParseError at [row, col]:[3,5] Message: Структуры документа XML должны начинаться и заканчиваться в одном и том же объекте. на com.sun.org.apache.xerces.internal.impl.XMLStreamReaderImpl.next(XMLStreamReaderImpl.java:598) на com.sun.xml.internal.stream.XMLEventReaderImpl.nextEvent(XMLEventReaderImpl.java:83) на librarian.controllers .BookCardController.saveToXML(BookCardController.java:138) ... еще 54' Я действительно не знаю почему, но дополнительно этот код очищает мой файл. - person Eugene; 10.05.2013
comment
Что ж, кажется, это исключение связано с тем, что я использовал один и тот же файл для ввода и вывода. После выбора другого целевого файла код начал работать, но... вывод был следующим: ‹индекс›‹/индекс›‹индекс›‹/индекс›‹индекс›‹/индекс›». И мне нужно включить Element в существующий XML. - person Eugene; 10.05.2013
comment
если оставить только это событие XMLEvent = eventReader.nextEvent(); писатель.добавить(событие); в цикле вы должны получить вывод == ввод, его нельзя потерять, попробуйте отладить - person Evgeniy Dorofeev; 10.05.2013
comment
О, это была моя ошибка. Я случайно удалил эту строку. Да, сильно работает. Большое тебе спасибо. Но теперь у меня есть еще один вопрос. Я попытался сделать это с файлом размером 40 МБ, и это заняло у меня 2,5-3 секунды, а если я использую файл размером 2 ГБ, это займет у меня почти 3 минуты! Есть ли возможность ускорить этот код? - person Eugene; 10.05.2013
comment
Не уверен, что это поможет, но стоит попробовать: измените FileWriter на новый BufferedOutputStream (новый FileInputStream (файл)) и FileInputStream на новый BufferedInputStream (новый FileInputStream (файл)) - person Evgeniy Dorofeev; 10.05.2013
comment
Я уже пробовал, но экономия всего 50-100 миллисекунд. Хорошо, большое спасибо. Вы действительно помогли мне! - person Eugene; 10.05.2013

Ну, это довольно ясно, почему он ведет себя так, как он это делает. На самом деле вы открываете существующий файл в режиме добавления вывода и записываете элементы в конце. Это явно противоречит тому, что вы пытаетесь сделать.

(Кроме того: я удивлен, что это работает так же хорошо, как и на самом деле, учитывая, что сторона ввода, вероятно, увидит элементы, которые сторона вывода добавляется в конец файла. И действительно, исключения, подобные приведенному в примере Евгения Дорофеева, являются чего я и ожидал. Проблема в том, что если вы пытаетесь читать и записывать текстовый файл одновременно, и либо читатель, либо писатель использует любую форму буферизации, явную или неявную, читатель может увидеть частичное состояния.)

Чтобы исправить это, вы должны начать с чтения из одного файла и записи в другой файл. Добавление не сработает. Затем вы должны сделать так, чтобы элементы, атрибуты, содержимое и т. д., считанные из входного файла, скопировались в выходной файл. Наконец, вам нужно добавить дополнительные элементы в соответствующих точках.


И есть ли возможность открыть файл XML в режиме типа RandomAccessFile, но записать в него методами StAX?

Нет. Это теоретически невозможно. Чтобы иметь возможность перемещаться по структуре XML-файла в «случайном» файле, вам сначала нужно проанализировать все это и построить индекс, где находятся все элементы. Даже если вы это сделаете, XML по-прежнему будет храниться в виде символов в файле, а произвольный доступ не позволит вам вставлять и удалять символы в середине файла.

Возможно, вам лучше всего будет объединить XSL и парсер в стиле SAX; например что-то вроде этой статьи IBM: http://ibm.com/developerworks/xml/library/x-tiptrax

person Stephen C    schedule 10.05.2013

Возможно, этот пример StAX Read-and-Write в учебнике JavaEE поможет: http://docs.oracle.com/javaee/5/tutorial/doc/bnbfl.html#bnbgq

Вы можете скачать учебные примеры здесь: https://java.net/projects/javaeetutorial/downloads

person kristjanroosild    schedule 07.06.2013