XmlParser和HtmlParser

经常要用的Xml和Html解决,实际上这个领域也有非常好的解决方案。

相对来说现在各种开源的Xml解析功能比较丰富,机制也比较灵活,但是由于他功能比较完善,干的事情比较多,所以性能方面也慢一点;另外,由于Xml天生是有严格格式的,所以问题不大,但是Html文件的内容是良莠不齐,有的网站经常缺少关闭标签,有的开始是大写,关闭是小写等等,没有严格遵守规范的时候,连Dom结构也解不正确,对于数据抓取程序来说,这就会严重影响正确性。

另外,一个重要的问题是数据遍历,一般来说在数据遍历方面,开源框架没有在性能做过充分优化,因此,如果要进行高速检索,就需要进行程序扩展。为此,本人编写一套XmlParser和HtmlParser,在数据校验方面做了删减,不支持进行数据校验,在容错性方面做了扩充,在Html解决时,即使格式不正确,在大多数情况下也可以返回正确的结果。最坏的情况也,也可以解决出Dom,但是Dom结构不一定正确,而不会出现崩溃或解析异常的问题。

还有一个是简体中文标签的支持能力,比如: <中文 属性1="1" 属性2="b" />

OK,费话少说,看看调用代码。

XmlStringParser parser = new XmlStringParser();
XmlDocument xmlDocument = parser.parse("<aa a=\"1\"><!--aa --><a a=\"aa\"></a></aa>");
上面就已经把xml解析好了。
HtmlStringParser parser = new HtmlStringParser();
HtmlDocument xmlDocument = parser.parse("<aa a=\"1\"><!--aa --><a a=\"aa\"></a></aa>");
上面就已经把html解析好了。

由于Xml及Html都是用得统一的接口,所以,会了Xml解析,Html也是一样样的。

解析出的Node,都实现了下面的接口,因此遍历方面也是非常方便的。

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();
}
为了避免接口太过庞大,因此把格式化的处理放在独立的结构中进行处理。
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;
}
要格式化输入的话,下面的代码就可以了:
HtmlDocument doc= new XmlStringParser().parse("<html 中='文'><head><title>aaa</title></head></html>");
HtmlFormater f = new HtmlFormater();
System.out.println(f.format(doc));
输出结果如下:

<html 中="文">
  <head>
    <title>
      aaa
    </title>
  </head>
</html>
上面已经演示了解析和格式化以及遍历,下面看看检索。

首先构建60*60*60,三层的Dom结构,也就是现在有216000个Dom节点

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));
		}
	}
}
然后对其进行节点查找,用两种方法进行10000次节点过滤:
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初始化用时" + (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));
}

下面看看时间耗费情况:

quick初始化用时385
QuickNameFilter用时376
fast初始化用时122
FastNameFilter用时330
可以看到fast的初始化时间及查找用时,都是最快的;而quick的初始化时间和查找用时相比要慢一些。但是请注意,这都是在216000个节点中查找10000次所耗费的时间。

那么再用传统的方式试一下---一般的开源方式也差不多在这个量级。

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));
}
运行结果:
Name初始化用时12
NameFilter用时83
但是,请注意,他的查询次数是10次,如果变成10000次,就是83000ms,也就是83秒之多。与Fast过滤方式相差了680倍之多。

小结:我们实现的Xml及HtmlParser确实是有自己独特的优点(学习成本低,Html和Xml解析方法一致,格式化输出,紧凑输出,容错性,查询效率高等等),也有不足(不支持DTD,XSD校验),在不需要校验的场景,需要容错性好及过滤性能高的场景下,是非常有优势的。


你可能感兴趣的:(java,html,xml,framework,parser)