如何提高Java解析pdm、ldm文件的性能

1、为什么要提高解析pdm、ldm文件的性能

本人目前负责的一个项目中,要用到将pdm文件导入到MySQL的功能。最近遇到麻烦了,由于程序是部署在阿里云上(内存只有2G),    且pdm文件比较大(约10M+),每次解析pdm文件10秒钟,就会导致tomcat自动关闭,搞的我们很尴尬。常规的办法:

  • 增加内存配置。考虑到成本问题,客户明确不会增加内存配置;
  • 减少pdm文件大小。业务上就有这么大的文件,如果拆成小文件,解析完再组合,反而增加实现的复杂度;
  • 优化代码。釜底抽薪,只能用这招了。

2、分析解析pdm、ldm文件常用的方法

解析节点的公共方法

    public Object evaluate(String path, Object object, QName returnType) {
        try {
            return XPathFactory.newInstance().newXPath().evaluate(path, object, returnType);
        } catch (XPathExpressionException e) {
            // 捕捉到异常不进行抛出操作,直接返回null,由后续方法进行判断处理。
            return null;
        }
    }

解析表的方法

public void parseTable(Node modelNode, MetaDomainDTO domain) {
        NodeList tableList = (NodeList) evaluate(PATH_O_TABLE, modelNode, XPathConstants.NODESET);
        List<MetaEntityDTO> entityList = new ArrayList<MetaEntityDTO>();
        for (int i = 0; i < tableList.getLength(); i++) {
            Node tableNode = tableList.item(i);// 获取table标签的解析对象
            MetaEntityDTO table = new MetaEntityDTO();// 获取table实例

            // 获取表id值
            Long id = parseIdToLong(getAttrValue(TAG_ATTR_ID, tableNode));
            // 获取<a:Name>标签内容作为描述信息
            String name = getAttrContent(PATH_A_NAME, tableNode);
            // 获取<a:Code>标签内容作为名称
            String code = getAttrContent(PATH_A_CODE, tableNode);
            // 获取<a:Comment>标签内容作为名称
            String comment = getAttrContent(PATH_A_COMMENT, tableNode);

            table.setEntityId(id);
            table.setEntityName(name);
            table.setEntityCode(code.toLowerCase());
            table.setEntityComment(comment);
            table.setDomainId(domain.getDomainId());
            table.setVirtualId(id);
            // 解析字段内容
            parseColumn(tableNode, table);
            // 解析键内容
            parseKey(tableNode, table);
            tableCache.put(id, table);
            entityList.add(table);
        }
        domain.setEntityList(entityList);
    }

以上是目前代码中的核心方法。为了定位到底是哪一步慢,我先减少pdm文件的大小,发现当文件是4M的时候解析需要15分钟,当文件是10M的时候,解析需要90分钟。我也观察了内存的使用情况,发现会经常发生GC。

pdm文件其实就是xml文件,我跳出了惯性思维,想想是否还有更加快的解析xml文件的方法呢?通过度娘,找到答案。

  (1)DOM解析 
    DOM是html和xml的应用程序接口(API),以层次结构(类似于树型)来组织节点和信息片段,映射XML文档的结构,允许获取和操作文档的任意部分,是W3C的官方标准
    【优点】
        ①允许应用程序对数据和结构做出更改。
        ②访问是双向的,可以在任何时候在树中上下导航,获取和操作任意部分的数据。
    【缺点】
        ①通常需要加载整个XML文档来构造层次结构,消耗资源大。
    【解析详解】
        ①构建Document对象:
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = bdf.newDocumentBuilder();
            InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(xml文件);
            Document doc = bd.parse(is);
        ②遍历DOM对象
            Document:    XML文档对象,由解析器获取
            NodeList:    节点数组
            Node:        节点(包括element、#text)
            Element:    元素,可用于获取属性参数


    (2)SAX(Simple API for XML)解析
    流模型中的"推"模型分析方式。通过事件驱动,每发现一个节点就引发一个事件,事件推给事件处理器,通过回调方法完成解析工作,解析XML文档的逻辑需要应用程序完成
    【优势】
        ①不需要等待所有数据都被处理,分析就能立即开始。
        ②只在读取数据时检查数据,不需要保存在内存中。
        ③可以在某个条件得到满足时停止解析,不必解析整个文档。
        ④效率和性能较高,能解析大于系统内存的文档。
    【缺点】
        ①需要应用程序自己负责TAG的处理逻辑(例如维护父/子关系等),文档越复杂程序就越复杂。
        ②单向导航,无法定位文档层次,很难同时访问同一文档的不同部分数据,不支持XPath。
    【原理】
        简单的说就是对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束时通知事件处理函数(回调函数),进行相应处理,直到文档结束
    【事件处理器类型】
        ①访问XML DTD:DTDHandler
        ②低级访问解析错误:ErrorHandler
        ③访问文档内容:ContextHandler
    【DefaultHandler类】
        SAX事件处理程序的默认基类,实现了DTDHandler、ErrorHandler、ContextHandler和EntityResolver接口,通常做法是,继承该基类,重写需要的方法,如startDocument()
    【创建SAX解析器】
        SAXParserFactory saxf = SAXParserFactory.newInstance();
        SAXParser sax = saxf.newSAXParser();
    注:关于遍历
        ①深度优先遍历(Depthi-First Traserval)
        ②广度优先遍历(Width-First Traserval)


    (3)JDOM(Java-based Document Object Model)
    Java特定的文档对象模型。自身不包含解析器,使用SAX
    【优点】
        ①使用具体类而不是接口,简化了DOM的API。
        ②大量使用了Java集合类,方便了Java开发人员。
    【缺点】
        ①没有较好的灵活性。
        ②性能较差。


    (4)DOM4J(Document Object Model for Java)
    简单易用,采用Java集合框架,并完全支持DOM、SAX和JAXP
    【优点】
        ①大量使用了Java集合类,方便Java开发人员,同时提供一些提高性能的替代方法。
        ②支持XPath。
        ③有很好的性能。
    【缺点】
        ①大量使用了接口,API较为复杂。


    (5)StAX(Streaming API for XML)
    流模型中的拉模型分析方式。提供基于指针和基于迭代器两种方式的支持,JDK1.6新特性
    【和推式解析相比的优点】
        ①在拉式解析中,事件是由解析应用产生的,因此拉式解析中向客户端提供的是解析规则,而不是解析器。
        ②同推式解析相比,拉式解析的代码更简单,而且不用那么多库。
        ③拉式解析客户端能够一次读取多个XML文件。
        ④拉式解析允许你过滤XML文件和跳过解析事件。
    【简介】
        StAX API的实现是使用了Java Web服务开发(JWSDP)1.6,并结合了Sun Java流式XML分析器(SJSXP)-它位于javax.xml.stream包中。XMLStreamReader接口用于分析一个XML文档,而XMLStreamWriter接口用于生成一个XML文档。XMLEventReader负责使用一个对象事件迭代子分析XML事件-这与XMLStreamReader所使用的光标机制形成对照。

3、总结解析pdm、ldm文件的代码

当文件很小的时候,用上面的多种方法,性能相差不多。但是当文件到达5M后,性能的差异就显示出来。

除了性能之外,还需要考虑具体的应用场景,选用不同的方案。

(1)DOM解析的方案,全部调整成(4)DOM4J方案。同样是10M的pdm文件,解析只要20秒,不是一个数量级的。核心源码如下:

public List<Table> loadAllTables(Element element1){
        List<Table> allTables = new ArrayList<Table>();

        //外键
        List<Reference> allReferences = loadAllReferences(element1);

        Element tableNode =(Element) element1.element("Tables");
        //如果主题域下面没有表关联,则直接返回
        if(tableNode==null){
            return allTables;
        }
        for(Object t1:tableNode.elements("Table")){
            Element t=(Element)t1;
            Table table = new Table();
            List<Column> allColumns = new ArrayList<Column>();
            String tableId = t.attributeValue("Id");
            String tableName = t.elementText("Name");
            String tableCode = t.elementText("Code");
            String tableComment = t.elementText("Comment");
            table.setTableId(tableId);
            table.setTableName(tableName);
            table.setTableCode(tableCode);
            table.setTableComment(tableComment);
            //主键
            List<String> allPKIds = loadAllPKIds(t);
            
            //Column信息添加
            Element columnNode = t.element("Columns");
            for(Object col1:columnNode.elements()){
                Element col=(Element)col1;
                Column column = new Column();
                String columnId = col.attributeValue("Id");
                String columnName = col.elementText("Name");
                String columnCode = col.elementText("Code");
                String columnComment = col.elementText("Comment");
                String dataType = col.elementText("DataType");
                Long length =  NumberUtils.toLong(col.elementText("Length"));
                Long precision = NumberUtils.toLong(col.elementText("Precision"));
//                Long mandatory = NumberUtils.toLong(col.elementText("LogicalAttribute.Mandatory"));
                boolean pk = false;
                //获取主键
                if(allPKIds.contains(columnId)){
                    pk = true;
                }
                boolean fk = false;
                //获取外键
                for(Reference ref : allReferences){
                    if(columnId.equals(ref.getFKId())){
                        fk = true;
                        table.setParentTableId(ref.getParentTableId());
                    }
                }
                column.setColId(columnId);
                column.setColName(columnName);
                column.setColCode(columnCode);
                column.setColComment(columnComment);
                column.setDataType(dataType);
                column.setLength(length);
                column.setPrecision(precision);
                column.setPK(pk);
                column.setFK(fk);
                allColumns.add(column);
            }
            table.setAllColumns(allColumns);
            allTables.add(table);
        }
        return allTables;
    }

欢迎大家一起讨论。

转载于:https://my.oschina.net/u/1986568/blog/852241

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值