Огромный 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
.