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

一、爲何要提升解析pdm、ldm文件的性能

本人目前負責的一個項目中,要用到將pdm文件導入到MySQL的功能。最近遇到麻煩了,因爲程序是部署在阿里雲上(內存只有2G),    且pdm文件比較大(約10M+),每次解析pdm文件10秒鐘,就會致使tomcat自動關閉,搞的咱們很尷尬。常規的辦法:html

  • 增長內存配置。考慮到成本問題,客戶明確不會增長內存配置;
  • 減小pdm文件大小。業務上就有這麼大的文件,若是拆成小文件,解析完再組合,反而增長實現的複雜度;
  • 優化代碼。釜底抽薪,只能用這招了。

二、分析解析pdm、ldm文件經常使用的方法

解析節點的公共方法java

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。tomcat

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:    元素,可用於獲取屬性參數dom


    (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所使用的光標機制造成對照。ui

三、總結解析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;
    }

歡迎你們一塊兒討論。

相關文章
相關標籤/搜索