經常要用的Xml和Html解決,實際上這個領域也有非常好的解決方案。
相對來說現在各種開源的Xml解析功能比較豐富,機制也比較靈活,但是由于他功能比較完善,干的事情比較多,所以性能方面也慢一點;另外,由于Xml天生是有嚴格格式的,所以問題不大,但是Html文件的內容是良莠不齊,有的網站經常缺少關閉標簽,有的開始是大寫,關閉是小寫等等,沒有嚴格遵守規范的時候,連Dom結構也解不正確,對于數據抓取程序來說,這就會嚴重影響正確性。
另外,一個重要的問題是數據遍歷,一般來說在數據遍歷方面,開源框架沒有在性能做過充分優化,因此,如果要進行高速檢索,就需要進行程序擴展。為此,本人編寫一套XmlParser和HtmlParser,在數據校驗方面做了刪減,不支持進行數據校驗,在容錯性方面做了擴充,在Html解決時,即使格式不正確,在大多數情況下也可以返回正確的結果。最壞的情況也,也可以解決出Dom,但是Dom結構不一定正確,而不會出現崩潰或解析異常的問題。
還有一個是簡體中文標簽的支持能力,比如: <中文 屬性1="1" 屬性2="b" />
OK,費話少說,看看調用代碼。
1 2 | XmlStringParser parser = new XmlStringParser(); XmlDocument xmlDocument = parser.parse("< aa a=\"1\"> <!--aa --> < a a=\"aa\"></ a ></ aa >"); |
1 2 | HtmlStringParser parser = new HtmlStringParser(); HtmlDocument xmlDocument = parser.parse("< aa a=\"1\"> <!--aa --> < a a=\"aa\"></ a ></ aa >"); |
由于Xml及Html都是用得統一的接口,所以,會了Xml解析,Html也是一樣樣的。
解析出的Node,都實現了下面的接口,因此遍歷方面也是非常方便的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | public interface Node<T extends Node<T>> extends ForEachProcessor<T> { ???? /** ????? * 獲取結點頭標簽相關內容 ????? * ????? * @return StringBuffer ????? */ ???? void getHeader(StringBuffer sb); ???? /** ????? * 返回子節點 ????? * ????? * @param name ????? * @return ????? */ ???? List<T> getSubNodes(String name); ???? /** ????? * 添加內容節點 ????? * ????? * @param content ????? */ ???? void addContent(String content); ???? /** ????? * 設置結點名稱 ????? * ????? * @param name ????? */ ???? void setNodeName(String name); ???? /** ????? * 獲取結尾標簽 ????? * ????? * @return StringBuffer ????? */ ???? void getFooter(StringBuffer sb); ???? /** ????? * 獲取根結點 ????? * ????? * @return T ????? */ ???? T getRoot(); ???? /** ????? * 設置父親節點 ????? * ????? * @param parent ????? */ ???? void setParent(T parent); ???? /** ????? * 返回節點名稱 ????? * ????? * @return ????? */ ???? String getNodeName(); ???? /** ????? * 返回父親節點 ????? * ????? * @return ????? */ ???? T getParent(); ???? /** ????? * 返回中間內容 ????? * ????? * @return ????? */ ???? StringBuffer getBody(); ???? /** ????? * 寫出數據 ????? * ????? * @param stream ????? * @throws IOException ????? */ ???? void write(OutputStream stream) throws IOException; ???? /** ????? * 返回節點類型 ????? * ????? * @return ????? */ ???? NodeType getNodeType(); ???? /** ????? * 返回屬性 ????? * ????? * @param attributeName ????? * @return ????? */ ???? String getAttribute(String attributeName); ???? /** ????? * 刪除屬性 ????? * ????? * @param attributeName ????? */ ???? void removeAttrivute(String attributeName); ???? /** ????? * 設置屬性值 ????? * ????? * @param attributeName ????? * @param value ????? */ ???? void setAttribute(String attributeName, String value); ???? /** ????? * 添加節點 ????? * ????? * @param node ????? *??????????? 要增加的節點 ????? * @return 如果增加成功,則返回node節點,否則返回null ????? */ ???? T addNode(T node); ???? /** ????? * 刪除節點 ????? * ????? * @param node ????? * @return 刪除的節點,如果當前節點中不包含node節點,則返回null ????? */ ???? T removeNode(T node); ???? /** ????? * 刪除指定節點 ????? * ????? * @param nodeName ????? * @return ????? */ ???? List<T> removeNode(String nodeName); ???? /** ????? * 獲取內容 ????? * ????? * @return ????? */ ???? String getContent(); ???? /** ????? * 變成StreamBuffer ????? * ????? * @return ????? */ ???? StringBuffer toStringBuffer(); ???? /** ????? * 設置內容 ????? * ????? * @param content ????? */ ???? void setContent(String content); ???? /** ????? * 返回屬屬性 ????? * ????? * @return ????? */ ???? Map<String, String> getAttributes(); ???? /** ????? * 返回子節點 ????? * ????? * @return ????? */ ???? List<T> getSubNodes(); ???? /** ????? * 是否單節點 ????? * ????? * @return ????? */ ???? boolean isSingleNode(); ???? /** ????? * 是否大小寫敏感 ????? * ????? * @return ????? */ ???? boolean isCaseSensitive(); ???? /** ????? * 根據大小寫相關返回名字 ????? * ????? * @param name ????? * @return ????? */ ???? String getCaseSensitiveName(String name); ???? /** ????? * 返回純文本內容 ????? * ????? * @return ????? */ ???? String getPureText(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public interface NodeFormater<E extends Node<E>, T extends Document<E>> { ???? /** ????? * 格式化文檔 ????? * ????? * @param doc ????? * @return String ????? */ ???? String format(T doc); ???? void setEncode(String encode); ???? /** ????? * 格式化文檔、 并在指定的輸出流中輸出 ????? * ????? * @param doc ????? * @param out ????? * @return void ????? * @throws IOException ????? */ ???? String format(E node); ???? void format(T doc, OutputStream out) throws IOException; ???? void format(E node, OutputStream out) throws IOException; } |
1 2 3 | HtmlDocument doc= new XmlStringParser().parse( "<html 中='文'><head><title>aaa</title></head></html>" ); HtmlFormater f = new HtmlFormater(); System.out.println(f.format(doc)); |
1 2 3 4 5 6 7 | < html 中="文"> ?? < head > ???? < title > ?????? aaa ???? </ title > ?? </ head > </ html > |
首先構建60*60*60,三層的Dom結構,也就是現在有216000個Dom節點
1 2 3 4 5 6 7 8 9 10 | XmlNode node = new XmlNode( "root" ); for ( int i = 0 ; i < 60 ; i++) { ???? XmlNode a = node.addNode( new XmlNode( "a" + i)); ???? for ( int j = 0 ; j < 60 ; j++) { ???????? XmlNode b = a.addNode( new XmlNode( "b" + j)); ???????? for ( int k = 0 ; k < 60 ; k++) { ???????????? b.addNode( new XmlNode( "c" + k)); ???????? } ???? } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public void testSpeed() { ???? long t21 = System.currentTimeMillis(); ???? QuickNameFilter quick = new QuickNameFilter(node); ???? long t22 = System.currentTimeMillis(); ???? System.out.println( "quick初始化用時" + (t22 - t21)); ???? long t1 = System.currentTimeMillis(); ???? String nodeName = null ; ???? for ( int x = 0 ; x < 10000 ; x++) { ???????? nodeName = quick.findNode( "b6" ).toString(); ???? } ???? long t2 = System.currentTimeMillis(); ???? System.out.println( "QuickNameFilter用時" + (t2 - t1)); } public void testSpeed1() { ???? long t21 = System.currentTimeMillis(); ???? FastNameFilter fast = new FastNameFilter(node); ???? long t22 = System.currentTimeMillis(); ???? System.out.println( "fast初始化<span></span><span></span>用時" + (t22 - t21)); ???? long t1 = System.currentTimeMillis(); ???? String nodeName = null ; ???? for ( int x = 0 ; x < 10000 ; x++) { ???????? nodeName = fast.findNode( "b6" ).toString(); ???? } ???? long t2 = System.currentTimeMillis(); ???? System.out.println( "FastNameFilter用時" + (t2 - t1)); } |
下面看看時間耗費情況:
1 2 3 4 | quick初始化用時 385 QuickNameFilter用時 376 fast初始化用時 122 FastNameFilter用時 330 |
那么再用傳統的方式試一下---一般的開源方式也差不多在這個量級。
1 2 3 4 5 6 7 8 9 10 11 12 13 | public void testSpeed2() { ???? long t11 = System.currentTimeMillis(); ???? NameFilter filter = new NameFilter(node); ???? long t12 = System.currentTimeMillis(); ???? System.out.println( "Name初始化用時" + (t12 - t11)); ???? long t1 = System.currentTimeMillis(); ???? String nodeName = null ; ???? for ( int x = 0 ; x < 10 ; x++) { ???????? nodeName = filter.findNode( "b6" ).toString(); ???? } ???? long t2 = System.currentTimeMillis(); ???? System.out.println( "NameFilter用時" + (t2 - t1)); } |
1 2 | Name初始化用時 12 NameFilter用時 83 |
小結:我們實現的Xml及HtmlParser確實是有自己獨特的優點(學習成本低,Html和Xml解析方法一致,格式化輸出,緊湊輸出,容錯性,查詢效率高等等),也有不足(不支持DTD,XSD校驗),在不需要校驗的場景,需要容錯性好及過濾性能高的場景下,是非常有優勢的。