科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件文本分析的三种典型设计模式

文本分析的三种典型设计模式

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

事件驱动:Parse-Handler模型 Tokenizer模型 文档对象:DOM模型

作者:许式伟 来源:CSDN 2008年1月18日

关键字: 设计模式 文本分析

  • 评论
  • 分享微博
  • 分享邮件
事件驱动:Parse-Handler模型(如:xml之SAX模型)

该模型主要有Parser和Handler两个组件。其原型大体如下:

class xxxHandler
{
public:
   
// any event sended from Parser
   ...
};

class xxxParser
{
public:
   xxxxParser(InputSource
* source);
 
   HRESULT parse(xxxxHandler
* handler)
   {
      
// analyze source and send event to handler
      ...
   }
};

该模型不规定Handler类型的详细规格,由Parser的实现者根据具体情况而定。
这种模型的核心思想就是由Parser类来具体分析文本的格式,而让信息真正的处理者Handler类从具体的格式中脱离出来,不再需要关心文本物理组织细节。


Tokenizer模型(如:编译器的词法分析器)

这种模型仅涉及一个Tokenizer组件。该组件负责将文本分解为一个个token。其原型大体如下:

class xxxTokenizer
{
public:
   xxxTokenizer(InputSource
* source);
 
   
//
   
// 成功返回S_OK,如果遇到eof返回S_FALSE。
   
//
   HRESULT next(TOKEN* token);
};

其中分析的结果以一个结构体TOKEN表示。这个结构体如何设计,同样视具体情况而定。通常它看起来是这样的:

struct TOKEN
{
 UINT type;
 union
 {
  DATATYPE1 data1; 
// 当type = type1时
  DATATYPE2 data2; // 当type = type2时
  ...
 };
};

有了Tokenizer,我们就可以轻易的遍历整个文档:

void visit(InputSource* source)
{
 TOKEN token;
 xxxTokenizer tokenizer(source);
 
while (tokenizer.next(&token) == S_OK)
 {
  print(token);
 }
}

token应当如何划分,其粒度如何,完全取决于设计者的考量。以以下一段xml文本为例:
 <elem attr="value">content</elem>
你可以划分为:

 <elem    // element start
 attr="value"  // attr-value pair
 content    // content
 </elem>    // element end

也可以将attr-value pair细分为三个token:attr, assign-symbol, value。
你甚至也可以将整个element作为一个token。

从广义上来说,我们文件系统提供的字节流本身已经是一个Tokenizer了,只不过它划分的token是一个个并无多少逻辑含义的character。

而我们后面提到的DOM模型,也可以算是一个Tokenizer。只不过它划分的token只有一个,就是DOM树,与文件系统的字节流走的是另一个极端。

Tokenizer方式与Parse-Handler方式设计思路,最大的不同在于具体处理信息的人主被动地位相异。在Tokenizer模式下,信息处理者调用Tokenizer得到分析数据,如果相邻的token存在上下文关系,你可以根据需要去取得下一个token,故处于主动地位。

而Parse-Handler模式相关死板一些,一方面Handler类实现者才是真正试图处理信息的人,但是实际上对信息的划分(token)却是由Parse规定的,未必完全符合Handler类的需求。另一方面在token存在上下文关系,当前接受的数据信息不足时,Handler类无法随心所欲的取得下一个token(因为从流程上它是被动的数据接受方),而只能暂时缓存数据,等待下一条信息的到来。

 

文档对象:DOM模型

DOM模型是最高级的一种模型。它的思路是将文档完整地读入内存,并提供数据访问接口。
DOM模型消耗的内存最多,可提供的服务(我们可以联想一下xml的诸多应用,如xslt等)也最为完整。

这里提到DOM模型消耗的内存最多,这种说法并不全面。例如,在将它与Parse-Handler模型相比时,我们只是计算了Parser的开销,而Handler类是客户实现的,内存开销多少,无从计算。

另一方面,由于DOM模型可以按自己的方式组织数据,它在内存开销上的可优化余地很大,并且客户在使用它时通常不再需要大量的内存分配操作;而Parse-Handler模型中,Handler类的实现者出现蹩脚的设计可能性非常高,计入Handler类的内存开销的话,有时甚至可能远远超过采用DOM模型。

因此我个人认为相对于DOM的能力而言,内存问题在DOM模型中并不算一个了不起的缺陷。实现者可以有很多技巧来进行内存优化。

但是DOM模型有一个问题,就是它一开始就将文档完整的读入了内存,使得它无法胜任那些对响应时间要求较高、希望能够渐进处理的应用。而这一点是采用Parse-Handler模型和Tokenizer模型的好处。


 

后记

这篇文章写得比较早,因为最近写WINX可视化开发工具相关的设计稿时用到,所以整理了下。我个人在文本文件和各种文档格式的文件打交道较多,多年来也算是形成了一定的经验。我个人现在越来越倾向于采用DOM模型来处理文件。原因在于采用DOM模型有很多优点:

  • DOM模型是提供了最高级的服务,模块的客户负担少。
     
  • 模块划分极其清晰,方便维护。通常DOM模型的内部仍然建立于SAX模型(或Tokenizer模型)上,但是这种依赖局限在DOM模型的内部。因此,程序通常会划分为3层:
       SAX(或Tokenizer) ==> DOM模型 ==> DOMClient(实际的应用)
     
  • 内存管理方面的可优化余地大。在多数情况下,我们建立的DOM模型是只读的(或允许进行少量修改),这种情形下,内存管理方案可以以最简洁的方式实现。下文我们详细讨论这一点。在此之前,我推荐你回顾一下《C++内存管理变革:最袖珍的垃圾回收器》。
     
  • 易获得更好的性能。虽然理论上来讲程序建立在SAX模型性能上可以获得更好的性能,但是经验表明,在团代开发的情形下,采用DOM模型的性能通常可优于建立在SAX模型之上的同样功能的复杂程序(不是简单打印或提取有限数据的情形)。

查看本文来源
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章