`
moshalanye
  • 浏览: 23938 次
  • 来自: ...
社区版块
存档分类
最新评论

大容量XML文件解析辅助--xml批量分解

阅读更多
   在项目里面遇到了一些被解析的xml文件超过30M 或 60M 以上的情况, 现在已经不好去说为什么不在一开始产生xml的情况下就把xml 做小点,但是遇到这个问题后,我只能解决问题了,解决问题同时害怕重复发明轮子,我也去看了下现有的xml 解析东西,jdom 的SAXBuilder和 dom4j 的SAXReader都是把XML文件一次读入,xml文件过来 会报溢出的异常    但即使SAXParser是可以批量读入解析,但它也是一次解析完,假设XML文件中有一万条数据,解析后就必须在内存中放这么多的对象     个人觉得这样有些不灵活,就自己做了个小东西来切分    但前提是这个xml文件得有文件头 <?xml version="1.0" encoding="GBK"?>   encoding必须跟文件编码格式一致 ,不然解析的时候会出乱码。
   
    个人水平有限,但很希望得到大家的指正,希望大家不吝啬手中的砖头
package searchRing.ring.util.xmlBufferTool;

import java.io.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;


public class XMLBufferTool {
    private static final int defaultLineCount = 10;
    private static final int defaultMaxOutputSize = 50;

    private static final Pattern elementPattern = Pattern.compile("<[a-zA-Z]+>");
    private static final Pattern charSetPattern = Pattern.compile("<[?][[0-9a-zA-Z]|[\\s]|[=]|[\"]|[.]|[-]]+[?]>");

    private StringBuffer xmlContentBuffer;


    /* just used to store and output the data divided */
    XMLOutputBuffer xmlOutput;

    private String charSetTitle = "";

    private String rootElemetMark = "";

    private String childElementMark = "";


    InputStreamReader bufferedReader;
    InputStream fileInputStream;


    public XMLBufferTool(String xmlFilePath) {

        this.xmlContentBuffer = new StringBuffer();

        try {

            this.fileInputStream = new FileInputStream(xmlFilePath);
//             bufferedReader = new InputStreamReader(fileInputStream, "UTF-8");
            String charSet = getCharSet(xmlFilePath);
            if (charSet != null)
                bufferedReader = new InputStreamReader(fileInputStream, charSet);
            else
                bufferedReader = new InputStreamReader(fileInputStream);
        } catch (FileNotFoundException fe) {
            fe.printStackTrace();
        } catch (UnsupportedEncodingException uee) {
            uee.printStackTrace();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }


        try {
            preparePaser();
        } catch (IOException ie) {
            ie.printStackTrace();
        }
    }


    public String getCharSetTitle() {
        return charSetTitle;
    }

    public String getRootElemetMark() {
        return rootElemetMark;
    }

    private String getCharSet(String filePath) throws IOException {
        char temp[] = new char[512];
        FileInputStream tempInput = new FileInputStream(filePath);
        InputStreamReader tempReader = new InputStreamReader(tempInput);

        int i = tempReader.read(temp);

        tempReader.close();
        tempInput.close();
        if (i < 0)
            return null;

        String tempStr = new String(temp);
        Matcher m = charSetPattern.matcher(tempStr);
        if (m.find()) {
            String charSetStr = tempStr.substring(m.start(), m.end());
            Pattern tempP = Pattern.compile("[\"][[0-9a-zA-Z]|[-]]+[\"]");
            Matcher tempM = tempP.matcher(charSetStr);
            if (tempM.find()) {
                String charSet = charSetStr.substring(tempM.start(), tempM.end());
                return charSet.substring(1, charSet.length() - 1);
            }
        }

        return null;
    }


    private void preparePaser() throws IOException {
        readSomeLine(defaultLineCount);
        Matcher m = charSetPattern.matcher(xmlContentBuffer);
        if (m.find()) {
            this.charSetTitle = this.xmlContentBuffer.substring(m.start(), m.end());
            this.xmlContentBuffer.delete(0, m.end());
        }

        m = elementPattern.matcher(xmlContentBuffer);
        if (m.find()) {
            this.rootElemetMark = this.xmlContentBuffer.substring(m.start(), m.end());
            this.xmlContentBuffer.delete(0, m.end());
        }

        m = elementPattern.matcher(xmlContentBuffer);
        if (m.find()) {
            this.childElementMark = this.xmlContentBuffer.substring(m.start(), m.end());
        }
        this.xmlOutput = new XMLOutputBuffer(this.childElementMark);

        parserBuffer();
    }


    private int readSomeLine(int lineCount) throws IOException {

        char buffer[] = new char[1024];
        int i = 0;
        int index = 0;
        /* be careful of the sequence of the boolean caculation */
        while (i++ < lineCount && (index = this.bufferedReader.read(buffer)) > 0) {
            xmlContentBuffer.append(buffer, 0, index);
        }

        return index;

    }


    private void parserBuffer() {

        int lastIndex = this.xmlContentBuffer.lastIndexOf(this.childElementMark);

        if (lastIndex > 0) {
            this.xmlOutput.append(this.xmlContentBuffer.substring(0, lastIndex));
            this.xmlContentBuffer.delete(0, lastIndex);
        }
    }

    public StringBuffer popDividedDataAfterParser() throws IOException {

        while (this.xmlOutput.getItemCount() < defaultMaxOutputSize) {
            int i = readSomeLine(defaultLineCount);
            parserBuffer();
            if (i < 0)
                break;
        }

        if (this.xmlOutput.getItemCount() == 0)
            return null;

        StringBuffer returnSB = this.xmlOutput.getXmlOutput();
        this.xmlOutput.clearBuffer();
        return returnSB.insert(0, this.rootElemetMark).append(this.rootElemetMark.replaceFirst("<", "</"));

    }


    public static void main(String args[]) throws Exception {
        String str = "F:/ringInfoXML/ringTime.xml";

        XMLBufferTool xmlb = new XMLBufferTool(str);

        StringBuffer s = xmlb.popDividedDataAfterParser();
        int i = 0;
        Matcher m = Pattern.compile("<ring>").matcher(s);
        while (m.find())
            i++;

        System.out.println(i);
        System.out.println(s);


    }

    private static class XMLOutputBuffer {
        private StringBuffer xmlOutput;
        private int itemCount;

        private Pattern markPattern;

        XMLOutputBuffer(String markStr) {
            this.markPattern = Pattern.compile(markStr);
            xmlOutput = new StringBuffer();
            itemCount = 0;
        }

        public void append(String str) {
            if (str == null || "".equals(str))
                return;
            this.xmlOutput.append(str);
            Matcher m = this.markPattern.matcher(str);
            while (m.find())
                this.itemCount++;
        }

        public void clearBuffer() {
            xmlOutput = new StringBuffer();
            this.itemCount = 0;
        }

        public StringBuffer getXmlOutput() {
            return xmlOutput;
        }

        public int getItemCount() {
            return itemCount;
        }
    }


}



   

代码中popDividedDataAfterParser() 输出的StringBuffer 可用来初始化一个 StringReader 再给dom4j 的saxReader去解析,这样联合一起用, 想处理多少,就先分出来解析多少,特别适合多线程的生产者和消费者的那种情况,希望对大家有用



分享到:
评论
14 楼 moshalanye 2009-03-18  
stax 提供的事件接口与 SAX 的event-based API 解决的问题是一样的,都是基于事件的,你说的startElement是sax 的event-based API。

stax 在解析xml上更强大些!

在别人的blog看到几篇介绍stax,有兴趣的可以去看看!写的很细心啊!

这边是Eastsun大哥的文章
http://eastsun.iteye.com/blog/129887 
13 楼 cocococoon 2009-03-16  
引用

StAX提供的定位机制  还没碰过   谢谢您的提示  


StAX技术的思路主要是通过streaming的方式读取XML流,然后你可以读取“流”中的一系列事件,如startElement, endElement等等,在每个事件中你可以读取一系列属性。针对这一特点,如果能够根据具体的XML文档结构,在你需要解析的节点上创建解析器,最大的好处就是高效解析巨大的XML文档:一边读取XML,遇到特定的节点的解析器才解析。
12 楼 ispring 2009-03-16  
Digester也是基于事件模型的,应该可以解决你这个问题,不过我没有试过。
Digester对SAX的事件模型进行了扩展,采用Stack来存储当前元素信息,调用方法非常简单,楼主可以试试看。
11 楼 nikos 2009-03-16  
sax解析的话,会出现数据不完整的问题,因为它每次读取的时候是2048的长度,这样可能一个完整句子,就被截断了,严重影响数据的质量

dom4j有一个类似sax的方法,也是基于事件触发机制的,每次只读出一段,然后在内存中形成一个树,一段一段的读,性能上来看是挺好的,但是我试的时候,速度会稍微慢点,这是dom4j官方网站上faq上推荐的(http://www.dom4j.org/dom4j-1.6.1/faq.html#large-doc)

dom4j还是很强大的,它提供了很多的实现方式,有兴趣可以一个个试一下

提示:很多人对dom4j有误解,以为用了dom4j,就跟dom不一样了,其实如果是用一般资料上介绍的方法,它跟dom是类似的,读取文件的时候,在内存中形成整棵树,数据量大了,就oom了
10 楼 xiaojing1005 2009-03-15  
现在我也碰到了这个问题.
在贵帖中挖掘一些思路.
9 楼 moshalanye 2008-07-11  
引用

你可以自己弄一个计数器统计,不在这个计数范围内的就不要处理了,它解析速度是很快的,顶多就浪费一点点cpu资源

如果处理起来麻烦的话,就用StAX提供的定位机制,不过偶没有实际用过,你可以试试看


谢谢您回答,
的确那个第二个需求,的确是我主观思想在作怪了,认为既然经过所有的节点,就必然会把所有节点的数据读取处理并存在内存中,的确也是可以要多少处理多少,就看自己怎么利用现有的条件进行控制了

StAX提供的定位机制  还没碰过   谢谢您的提示  


您说的那个计数器,我当时也有这样想,但是没去实践,也就是您说到的那点,以为每个点都看是否处理,怕浪费cpu,有空还是要实践下,这样才有更多的了解  
在此谢谢各位的回复  
这个是我发的第一个贴子,也是让人很开心的一个开始
8 楼 spiritfrog 2008-07-11  
kusix 写道
同样碰到过LZ的问题,70M的XML,一读服务器就OOM了

研究过sax,感觉很不好用,需要把已有的DOM结构的程序全部废掉重写。

目前采用的是用NIO内存映射方式读文件,然后把XML一段段摘取出来,分段解析处理,

速度还是很不错的,服务器也不OOM了。


如何一段段摘取出来的?
7 楼 quaff 2008-07-10  
看看xquery是不是基于流操作
6 楼 kusix 2008-07-10  
同样碰到过LZ的问题,70M的XML,一读服务器就OOM了

研究过sax,感觉很不好用,需要把已有的DOM结构的程序全部废掉重写。

目前采用的是用NIO内存映射方式读文件,然后把XML一段段摘取出来,分段解析处理,

速度还是很不错的,服务器也不OOM了。
5 楼 Readonly 2008-07-10  
sax的event处理是基于流操作的,不会在内存中构建整个文档树,可以满足你的第2点,但是它只相当于一个简单的iterator,并没有定位机制,你的第一点做不到,不过你可以自己弄一个计数器统计,不在这个计数范围内的就不要处理了,它解析速度是很快的,顶多就浪费一点点cpu资源

如果处理起来麻烦的话,就用StAX提供的定位机制,不过偶没有实际用过,你可以试试看
4 楼 moshalanye 2008-07-10  
   我做的这个出现的需求可以是:
1。 我不需要把XML所有的数据解析完成后,再对数据做处理。  比如xml中有一万条数据(一条数据是一个子节点),我第一次只处理解析1-100的数据,并对数据做业务处理 , 下次再处理101-200的数据,再做业务处理    这样我就不用等全解析完后,再对所有解析后的数据做业务处理,也就是我要几条,就可以解析几条


2。一次性把一个xml 解析后,(假设xml 有几万条数据,那么在内存中就要有这么多的空间来存放)这样挺耗空间的,也不好做成buffer缓存的效果
3 楼 Readonly 2008-07-10  
是的,SAX的event-based API就是你给出的那个接口,具体用法它的文档上就有

你说的不灵活,偶不明白你的具体需求,也就不好给建议了...
2 楼 moshalanye 2008-07-10  
引用

SAX有event-based API,没有必要自己造轮子


我接触xml的时间不是太长,我也希望能获得更多信息,我看了下您的建议,不知道这个event-based 是否是指的org.xml.sax.ContentHandler 接口中的那些  

public void startDocument ()throws SAXException;

public void startElement (
                    String namespaceURI, String localName,
	           String qName, 
                    Attributes atts)
	               throws SAXException;

。。。。。。。。。。


   这些方法,如果是的话,那么这些handle中的方法,实现后在saxParser解析的时候是一直批量的解析下去,总不能解析到了一定数量的 Element 再停下来吧? 我当时也是觉得这样不灵活,用信号量使用wait()来停,我又觉得不是太优雅,所以就没用这个了
    应该还是我知道的东西太少,希望楼上的大虾再指导下,最好有个简单的例子就更加万分感谢了!
1 楼 Readonly 2008-07-10  
SAX有event-based API,没有必要自己造轮子

相关推荐

Global site tag (gtag.js) - Google Analytics