Огромный XML файл в текстовые файлы

У меня есть огромный XML файл (15 ГБ). Я хочу преобразовать тег "text" в XML файл на одну страницу.

Пример файла XML:

<root>
    <page>
        <id> 1 </id>
        <text>
        .... 1000 to 50000 lines of text
        </text>
    </page>
    ... Like wise 2 Million `page` tags
</root>

Я изначально использовал парсер DOM, но он выбрасывает JAVA OUT OF MEMORY (Действительный). Теперь я написал код JAVA, используя STAX. Он работает хорошо, но производительность очень медленная.

Это код, который я написал:

 XMLEventReader xMLEventReader = XMLInputFactory.newInstance().createXMLEventReader(new FileInputStream(filePath));
    while(xMLEventReader.hasNext()){
      xmlEvent = xMLEventReader.nextEvent();

    switch(xmlEvent.getEventType()){
    case XMLStreamConstants.START_ELEMENT:
    if( element == "text")
      isText    = true;
    break;
    case XMLStreamConstants.CHARACTERS:
      chars = (Characters) xmlEvent;
      if(! (chars.isWhiteSpace() || chars.isIgnorableWhiteSpace()))
               if(isText)
              pageContent += chars.getData() + '\n';
      break;
    case XMLStreamConstants.END_ELEMENT:
      String elementEnd = (((EndElement) xmlEvent).getName()).getLocalPart();
      if( elementEnd == "text" )
      {
          createFile(id, pageContent);
          pageContent = "";
          isText = false;
      }
      break;
    }
}

Этот код работает хорошо. (Игнорируйте любые незначительные ошибки). Согласно моему пониманию, XMLStreamConstants.CHARACTERS выполняет итерацию для каждого текстового тега. Если в теге TEXT имеется 10000 строк, XMLStreamConstants.CHARACTERS выполняет итерацию для следующих 10000 строк. Есть ли лучший способ повысить производительность??

Ответ 1

Что такое pageContent? Кажется, это String. Одной простой оптимизацией сразу станет использование StringBuilder; он может добавлять строки без необходимости создания совершенно новых копий строк, таких как String += (вы также можете создать его с первоначальной зарезервированной емкостью для уменьшения перераспределения памяти и копий, если у вас есть представление о длине, чтобы начать с).

Конкатенация String - медленная операция, потому что строки неизменяемы в Java; каждый раз, когда вы вызываете a += b, он должен выделить новую строку, скопировать a в нее, а затем скопировать b в конец ее; делая каждую конкатенацию O (n) wrt. общая длина двух строк. То же самое касается добавления отдельных символов. StringBuilder, с другой стороны, имеет те же рабочие характеристики, что и ArrayList при добавлении. Итак, где у вас есть:

pageContent += chars.getData() + '\n';

Вместо этого измените pageContent на a StringBuilder и выполните:

pageContent.append(chars.getData()).append('\n');

Также, если у вас есть предположение о верхней границе длины одной из этих строк, вы можете передать ее конструктору StringBuilder, чтобы выделить начальный объем емкости и уменьшить вероятность перераспределения памяти и полной копии должно быть сделано.

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

Ответ 2

Я вижу несколько возможных решений, которые могут вам помочь:

  • Используйте BufferedInputStream вместо простого FileInputStream для уменьшения числа операций с дисками
  • Рассмотрите возможность использования StringBuilder для создания вашей страницы. Контент, а не привязка к строкам.
  • Увеличьте свою кучу Java (-Xmx), если вы связаны памятью с вашим примером 2GB.

В таких случаях может быть довольно интересно подключить профилировщик кода (например, Java VisualVM), так как тогда вы можете точно видеть какие вызовы методов замедляются в вашем коде. Затем вы можете оптимизировать фокусировку.

Ответ 3

Если синтаксический анализ файла XML является основной проблемой, рассмотрите возможность использования VTD-XML, а именно расширенную версию, поддерживающую файлы до 256 ГБ.

Поскольку он основан на неэкстрактивном анализе документов, он достаточно эффективен для памяти и использует его для запроса/извлечения текста с использованием XPath также очень быстро. Вы можете прочитать более подробную информацию об этом подходе и VTD-XML из здесь.

Ответ 4

Попробуйте разобрать парсер SAX, потому что DOM попытается разобрать весь контент и поместить его в память. Из-за этого вы получаете исключение памяти. Анализатор SAX не будет анализировать весь контент на одном участке.

Ответ 5

Код выглядит стандартным. Однако вы могли бы попытаться обернуть свой FileInputStream в BufferedInputStream и сообщить нам, если это поможет? BufferedInputstream экономит несколько нативных вызовов ОС, поэтому есть шансы на повышение производительности. Вы должны играть с размером буфера, чтобы получить оптимальную производительность. Установите определенный размер в зависимости от распределения памяти JVM.

Ответ 6

  • Используйте BufferedInputStream вокруг FileInputStream.
  • Не объединяйте данные. Это полная трата времени и пространства, потенциально много места. Запиши это сразу, как только получишь. Для этого используйте BufferedWriter вокруг a FileWriter.