大型XML文件的处理方法
在实际工作中,我们经常需要处理各种大型的XML文件,通常在10M以上,且一般规则比较简单,但是对内存和时间的要求都比较高。在这种情况下,不能使用DOM方式,因为DOM方式必须在解析文件之前把整个文档装入内存,在处理大型文件时其性能下降的非常厉害,通常会导致OutOfMemory异常。
本文推荐使用的是Simple API for XML(SAX)方式,在SAX解析器装载XML文件时,它遍历文件文档并在其主机应用程序中产生事件(经由回调函数、指派函数或者任何可调用平台完成这一功能)表示这一过程。在解析同等大小的文档时SAX通常会相比DOM提供更好的性能(因为DOM必须遍历树结构)。此外,与DOM相比,因为在给定的时间之内只需要XML文档的一部分装入内存,所以SAX 通常在处理更大文件时内存的利用效率也来得更高,SAX应用程序的事件处理结构一般意味着SAX应用程序是针对特定文件结构定制构建的,而DOM应用程序则更具一般性。
下面用实例来说明,有一文件(http://zhouxianli.blogdriver.com/zhouxianli/inc/xmlfile.xml),其中的item和area都是多条的,通常每次处理时的文件大小在10M以上,使用DOM方式开发时直接OutOfMemory。如是按照SAX方式编写如下代码:
package test.xml; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; public class SaxTest { public static void main(String args[]) throws Exception { System.out.println("UseMemory=" + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime() .freeMemory())); SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser = factory.newSAXParser(); ItemProcessHandler handler = new ItemProcessHandler(); System.out.println("UseMemory=" + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime() .freeMemory())); // saxParser.parse("src/main/java/test/xml/xmlfile.xml", handler); saxParser.parse("src/main/java/test/xml/bigdata.xml", handler); System.out.println("UseMemory=" + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime() .freeMemory())); } } class ItemProcessHandler extends DefaultHandler { // 用于存放当前元素的值 private StringBuilder currentValue = new StringBuilder(); // Item中的各个属性 private String areaid; private String key; private String intervaltype; private String value; private String remark; public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { // 按item遍历的,所以在此初始化。因为areaid不在item块中,所以不清除 if (qName.equals("item")) { key = null; intervaltype = null; value = null; remark = null; } // 清空当前元素值 currentValue.setLength(0); } public void endElement(String uri, String localName, String qName) throws SAXException { // 对每个元素获取其值 if (qName.equals("areaid")) { areaid = currentValue.toString(); } else if (qName.equals("key")) { key = currentValue.toString(); } else if (qName.equals("intervaltype")) { intervaltype = currentValue.toString(); } else if (qName.equals("value")) { value = currentValue.toString(); } else if (qName.equals("remark")) { remark = currentValue.toString(); } else if (qName.equals("item")) { // item元素结束时,处理Item processItem(areaid, key, intervaltype, value, remark); } } public void characters(char ch[], int start, int length) throws SAXException { currentValue.append(ch, start, length); } // 处理Item的方法体,注意处理异常 public void processItem(String areaid, String key, String intervaltype, String value, String remark) { try { // System.out.println("areaid=" + areaid + ",key=" + key // + ",intervaltype=" + intervaltype + ",value=" + value // + ",remark=" + remark); } catch (Exception e) { throw new IllegalStateException(e); } } }
注:上述是我建议的处理方式,将属性赋值放在endElement中处理,将实际业务操作放在processXXX中处理,需要记住的是,不要采取数据解析和业务处理分离的方式(如:让handler返回一个List,得到List后循环处理数据的方式),因为那样会带来大量额外的内存消耗(List的数据所需内存)。
首先,使用xmlfile.xml(700字节)测试,打印
UseMemory=277952
UseMemory=497544
UseMemory=636760
然后,使用bigdata.xml(11,378,841字节)测试,打印
UseMemory=277952
UseMemory=497544
UseMemory=792512
对比两个结果,可以发现处理bigdata.xml这么大的数据时,实际消耗的内存也是非常小的,适合于处理大型XML文件。