文本分析的三種典型設計模式

文本分析的三種典型設計模式

許式偉
2004-10-27


事件驅動: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模型之上的同樣功能的複雜程序(不是簡單打印或提取有限數據的情形)。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章