Как обрабатывать большие (50 ГБ) XML файлы в Java

Вопрос:В настоящее время я пытаюсь использовать SAX Parser, но около 3/4 через файл, который он полностью замораживает, я попытался выделить больше памяти и т.д., но не получал никаких улучшений. Есть ли способ ускорить это? Лучший метод? Разделил его на голые кости, поэтому теперь у меня есть следующий код, и при запуске в командной строке все

Вопрос:

В настоящее время я пытаюсь использовать 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; } } Лучший ответ:

Ваш код синтаксического анализа, скорее всего, работает нормально, но объем данных, которые вы загружаете, вероятно, слишком велик для хранения в памяти 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, как определено здесь, и построить там логику, которая буферизует данные и отправляет их для дальнейшей обработки в кусках.

Ответ №1

Это действительно проблема: pages.add(page);. На самом деле SAX очень дружелюбен к памяти, а использование памяти не зависит от размера входного файла.

Мы разработали генератор кода, который генерирует код на основе XSD (если у вас есть его, вы можете сгенерировать его из исходного документа). Этот продукт основан на SAX и без видимости обрабатывает файлы с несколькими ГБ (самое большое, что мы использовали, – 22 ГБ). Это похоже на подход, описанный доном Роба здесь. Единственное, что вам нужно сделать, это реализовать интерфейс процессора.

В процессоре runtime (java) используется файл конфигурации (файл свойств java), который позволяет вам подписаться на типы схем, которые вам интересны. Если вам нравится больше информации об этом, посмотрите здесь:

Оцените статью
Добавить комментарий