Как обрабатывать большие (50 ГБ) XML файлы в Java
В настоящее время я пытаюсь использовать SAX Parser, но около 3/4 через файл, который он полностью замораживает, я попытался выделить больше памяти и т.д., но не получал никаких улучшений.
Есть ли способ ускорить это? Лучший метод?
Разделил его на голые кости, поэтому теперь у меня есть следующий код, и при запуске в командной строке все равно не происходит так быстро, как хотелось бы.
Запустив его с помощью "java -Xms-4096m -Xmx8192m -jar reader.jar", я получил превышение верхнего предела GC вокруг статьи 700000
Main:
public class Read {
public static void main(String[] args) {
pages = XMLManager.getPages();
}
}
XMLManager
public class XMLManager {
public static ArrayList<Page> getPages() {
ArrayList<Page> pages = null;
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
SAXParser parser = factory.newSAXParser();
File file = new File("..\\enwiki-20140811-pages-articles.xml");
PageHandler pageHandler = new PageHandler();
parser.parse(file, pageHandler);
pages = pageHandler.getPages();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return pages;
}
}
PageHandler
public class PageHandler extends DefaultHandler{
private ArrayList<Page> pages = new ArrayList<>();
private Page page;
private StringBuilder stringBuilder;
private boolean idSet = false;
public PageHandler(){
super();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
stringBuilder = new StringBuilder();
if (qName.equals("page")){
page = new Page();
idSet = false;
} else if (qName.equals("redirect")){
if (page != null){
page.setRedirecting(true);
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (page != null && !page.isRedirecting()){
if (qName.equals("title")){
page.setTitle(stringBuilder.toString());
} else if (qName.equals("id")){
if (!idSet){
page.setId(Integer.parseInt(stringBuilder.toString()));
idSet = true;
}
} else if (qName.equals("text")){
String articleText = stringBuilder.toString();
articleText = articleText.replaceAll("(?s)<ref(.+?)</ref>", " "); //remove references
articleText = articleText.replaceAll("(?s)\\{\\{(.+?)\\}\\}", " "); //remove links underneath headings
articleText = articleText.replaceAll("(?s)==See also==.+", " "); //remove everything after see also
articleText = articleText.replaceAll("\\|", " "); //Separate multiple links
articleText = articleText.replaceAll("\\n", " "); //remove new lines
articleText = articleText.replaceAll("[^a-zA-Z0-9- \\s]", " "); //remove all non alphanumeric except dashes and spaces
articleText = articleText.trim().replaceAll(" +", " "); //convert all multiple spaces to 1 space
Pattern pattern = Pattern.compile("([\\S]+\\s*){1,75}"); //get first 75 words of text
Matcher matcher = pattern.matcher(articleText);
matcher.find();
try {
page.setSummaryText(matcher.group());
} catch (IllegalStateException se){
page.setSummaryText("None");
}
page.setText(articleText);
} else if (qName.equals("page")){
pages.add(page);
page = null;
}
} else {
page = null;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
stringBuilder.append(ch,start, length);
}
public ArrayList<Page> getPages() {
return pages;
}
}
Ответы
Ответ 1
Ваш код синтаксического анализа, скорее всего, работает нормально, но объем данных, которые вы загружаете, вероятно, слишком велик для хранения в памяти ArrayList
.
Вам нужен какой-то конвейер для передачи данных в его фактическое место назначения
немедленно сохраните все в памяти.
То, что я иногда делал для такого рода ситуаций, похоже на следующее.
Создайте интерфейс для обработки одного элемента:
public interface PageProcessor {
void process(Page page);
}
Поставить реализацию этого в PageHandler
через конструктор:
public class Read {
public static void main(String[] args) {
XMLManager.load(new PageProcessor() {
@Override
public void process(Page page) {
// Obviously you want to do something other than just printing,
// but I don't know what that is...
System.out.println(page);
}
}) ;
}
}
public class XMLManager {
public static void load(PageProcessor processor) {
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
SAXParser parser = factory.newSAXParser();
File file = new File("pages-articles.xml");
PageHandler pageHandler = new PageHandler(processor);
parser.parse(file, pageHandler);
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Отправлять данные на этот процессор вместо того, чтобы помещать его в список:
public class PageHandler extends DefaultHandler {
private final PageProcessor processor;
private Page page;
private StringBuilder stringBuilder;
private boolean idSet = false;
public PageHandler(PageProcessor processor) {
this.processor = processor;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//Unchanged from your implementation
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
//Unchanged from your implementation
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// Elide code not needing change
} else if (qName.equals("page")){
processor.process(page);
page = null;
}
} else {
page = null;
}
}
}
Конечно, вы можете заставить свой интерфейс обрабатывать фрагменты нескольких записей, а не только один, и локально собирать страницы PageHandler
в меньшем списке и периодически отсылать список для обработки и очищать список.
Или (возможно, лучше) вы можете реализовать интерфейс PageProcessor
, как определено здесь, и построить там логику, которая буферизует данные и отправляет их для дальнейшей обработки в кусках.
Ответ 2
Это действительно проблема: pages.add(page);
. На самом деле SAX очень дружелюбен к памяти, а использование памяти не зависит от размера входного файла.
Мы разработали генератор кода, который генерирует код на основе XSD (если у вас есть его, вы можете сгенерировать его из исходного документа). Этот продукт основан на SAX и без видимости обрабатывает файлы с несколькими ГБ (самое большое, что мы использовали, - 22 ГБ). Это похоже на подход, описанный доном Роба здесь. Единственное, что вам нужно сделать, это реализовать интерфейс процессора.
В процессоре runtime (java) используется файл конфигурации (файл свойств java), который позволяет вам подписаться на типы схем, которые вам интересны. Если вам нравится больше информации об этом, посмотрите здесь: http://www.xml2java.net/xml-to-java-data-binding-for-big-data/