如何在Java中解析大(50 GB)XML文件

目前我试图使用SAX Parser,但是大约3/4通过文件它只是完全冻结,我已经尝试分配更多的内存等但没有得到任何改进。

有什么方法可以加快速度吗? 一个更好的方法?

将它剥离到裸骨,所以我现在有以下代码,当在命令行中运行时,它仍然没有我想要的那么快。

使用“java -Xms-4096m -Xmx8192m -jar reader.jar”运行它我得到的GC开销限制超出了文章700000

主要:

public class Read { public static void main(String[] args) { pages = XMLManager.getPages(); } } 

XMLManager

 public class XMLManager { public static ArrayList getPages() { ArrayList 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; } } 

页面处理器

 public class PageHandler extends DefaultHandler{ private ArrayList 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(.+?)", " "); //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 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接口,并在那里构建逻辑,缓冲数据并将其发送以进行进一步的块处理。

Don Roby的方法让我想起了我创建一个旨在解决这个特定问题的代码生成器的方法(早期版本是在2008年构思的)。 基本上每个complexType都具有Java POJO等效项,并且当上下文更改为该元素时,将激活特定类型的处理程序。 我使用这种方法进行SEPA,事务处理和例如discogs(30GB)。 您可以使用propeties文件以声明方式指定要在运行时处理的元素。

XML2J一方面使用complexTypes到Java POJO的映射,但允许您指定要监听的事件。 例如

 account/@process = true account/accounts/@process = true account/accounts/@detach = true 

本质是在第三行。 分离可确保个人帐户不会添加到帐户列表中。 所以它不会溢出。

 class AccountType { private List accounts = new ArrayList<>(); public void addAccount(AccountType tAccount) { accounts.add(tAccount); } // etc. }; 

在您的代码中,您需要实现流程方法(默认情况下,代码生成器生成一个空方法:

 class AccountsProcessor implements MessageProcessor { static private Logger logger = LoggerFactory.getLogger(AccountsProcessor.class); // assuming Spring data persistency here final String path = new ClassPathResource("spring-config.xml").getPath(); ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(path); AccountsTypeRepo repo = context.getBean(AccountsTypeRepo.class); @Override public void process(XMLEvent evt, ComplexDataType data) throws ProcessorException { if (evt == XMLEvent.END) { if( data instanceof AccountType) { process((AccountType)data); } } } private void process(AccountType data) { if (logger.isInfoEnabled()) { // do some logging } repo.save(data); } } 

请注意, XMLEvent.END标记元素的结束标记。 因此,当您处理它时,它就完成了。 如果必须将它(使用FK)与数据库中的父对象相关联,则可以处理父级的XMLEvent.BEGIN ,在数据库中创建占位符并使用其键与其子级存储。 在最终的XMLEvent.END您将更新父级。

请注意,代码生成器生成您需要的所有内容。 您只需实现该方法,当然还有DB胶代码。

有些样本可以帮助您入门。 代码生成器甚至可以生成您的POM文件,因此您可以在生成后立即构建项目。

默认的处理方法是这样的:

 @Override public void process(XMLEvent evt, ComplexDataType data) throws ProcessorException { /* * TODO Auto-generated method stub implement your own handling here. * Use the runtime configuration file to determine which events are to be sent to the processor. */ if (evt == XMLEvent.END) { data.print( ConsoleWriter.out ); } } 

下载:

首先mvn clean install核心(它必须在本地maven repo中),然后是生成器。 并且不要忘记根据XML2J_HOME中的指示设置环境变量XML2J_HOME