熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

構建用於正則表達式的抽象Java API

2022-06-13   來源: Java核心技術 

  在我的經驗中大多數 Java 開發人員都需要解析某種文本通常這意味著他們最初要花一些時間使用象 indexOf 或 substring 那樣的與 Java 字符串相關的函數或方法並且希望輸入格式永遠不變但是如果輸入格式改變那麼用於讀取新格式的代碼維護起來就會變得更復雜更困難最後代碼可能需要支持自動換行(word wrapping)區分大小寫等

  由於邏輯變得更加復雜所以維護也變得很困難因為任何更改都可能產生副作用並使文本解析器的其它部分停止工作所以開發人員需要時間修正這些小錯誤

  有一定 Perl 經驗的開發人員可能也有過使用正則表達式的經驗如果夠幸運(或優秀)的話這位開發人員能夠說服團隊其余的人(或至少是團隊領導)使用這項技術新的方法將取消編寫用來調用 String 方法的多行代碼它意味著將解析器邏輯的核心委托出去並替換為 regexp 庫

  接受了有 Perl 經驗的開發人員的建議後團隊必須選擇哪個 regex 實現最適合他們的項目然後他們需要學習如何使用它

  在簡要地研究了從因特網上找到的眾多可選方案後假設團隊決定從人們更熟悉的庫中選擇一個使用如屬於 Jakarta 項目的 Oro接下來對解析器進行較大程度地重構或幾乎重新編寫並且解析器最終使用了 Oro 的類如 PerlCompilerPerlMatcher 等

  這一決定的後果很明顯代碼與 Jakarta Oro 的類緊密地耦合在一起

  團隊承擔了風險因為不知道非功能性需求(如性能或線程模型)是否將得到滿足

  團隊已花費時間和財力來學習並重新編寫代碼以使它使用 regexp 庫如果他們的決定是錯誤的並且選擇了新的庫則這一工作在成本上將不會有很大區別因為將需要再次重新編寫代碼

  即使庫工作正常如果他們決定應該遷移到全新的庫(例如包括在 JDK 中的庫)怎麼辦?

  去耦的好處

  有沒有辦法使團隊知道哪個實現最適合他們的需要呢(不僅現在能將來也能)?讓我們試著尋找答案

  避免依賴任何特定的實現

  前面的情形在軟件工程中十分常見在有些情況中這樣的情形會導致較大的投資和較長的延期當不了解所有後果就作出決定而且決策制定人不太走運或缺乏必需的經驗時就常常會發生這種情況

  可將該情形概括如下

  您需要某種提供者

  您沒有選擇最佳提供者的客觀標准

  您希望能用最低的成本來評估所有的待選項

  所作的決定不應將您束縛在所選的提供者上

  這一問題的解決方法是使代碼更加獨立於提供者這引入了新的層 ? 同時去除客戶機和提供者的耦合的層

  在服務器端開發中很容易找到使用該方法的模式或體系結構下面引用一些示例

  對於 JEE您主要關注如何構建應用程序而不是應用程序服務器的細節

  數據訪問對象(Data Access ObjectDAO)模式隱藏了如何訪問數據庫(或 LDAP 服務器XML 文件等)的細節和復雜性因為它提供了訪問抽象持久存儲層的方法而您則不需要在客戶機代碼中處理數據庫問題(數據實際存儲在哪裡)這不是四人組(Gang of FourGoF)模式而是 Sun 的 JEE 最佳實踐的一部分

  在假想的開發團隊示例中他們正在尋找這樣的層

  抽象所有正則表達式實現背後的概念團隊就可以著重學習和理解這些概念他們所學的可以應用到任何實現或版本

  支持新的庫且沒有副作用基於插件體系結構動態選擇執行 regexp 模式的實際庫並且適配器不會被耦合新庫僅會引入對新適配器的需要

  提供比較不同可選方案的方法一個簡單的基准實用程序就可以顯示有趣的性能測量結果如果對每個實現都執行這樣的實用程序團隊就會獲得有價值的信息並能選擇最好的可選方案

  聽起來不錯但……

  任何去耦方法都至少有一個缺點如果客戶機代碼僅需要一個實現所提供的特定功能怎麼辦?您不能使用任何其它實現因此您最終將代碼與該實現耦合也許將來會在這方面有所改善但您現在卻束手無策

  這樣的示例並不象您想的那樣少在 regexp 領域中一些編譯器選項僅被某些實現支持如果您的客戶機代碼需要這種特定的功能那麼這個一般層是不夠的 ? 至少從迄今對它描述來看是不夠的

  附加層是否應支持每個實現的所有非公共功能並且如果選擇了不支持該實現的附加層則拋出異常?那可以是一種解決方案但它並不支持僅定義公共抽象概念這一最初目標

  有一個 GoF 模式非常適合這種情形職責鏈(Chain of Responsibility)它在設計中引入了另一種間接方法用這種方法客戶機代碼向能處理其所發消息的實體列表發送消息或命令列表項被組織成鏈因此消息可按順序被處理並且在到達鏈尾之前被用掉

  在這種情況中可以通過特殊類型的消息對僅被某些實現支持的特定功能建模由鏈中的每一項根據其是否了解這些功能來決定是否將該消息傳給下一項

  定義一個公共 API

  這裡講述的 API 名為 RegexpPlugin已將它設計成遵循剛剛討論的方法並且它在 regexp 庫和使用該庫的代碼之間支持去耦

  RegexpPlugin

  在以下示例中我將總結一下使用具體實現(Jakarta Oro)和使用 RegexpPlugin API 之間的差別

  我從一個非常簡單的 regexp 開始假定您必須要解析的文本只是人名您接收的格式是象 John A Smith 這樣的內容而您只想獲取名字(John)但您不知道單詞由什麼分隔是空格換行符制表符還是這些字符的組合能處理這樣的輸入格式的 regexp 只是 *s*(*?)s+*我將一步一步地說明如何使用該 regexp 來抽取信息

  第一部分是點號和星號字符 *它們在這裡表示任意數量的空格和 (*?) 組之前的任何字符第二部分比較引人注意(因為它被圓括號括起來)問號表示取第一個符合條件的項

  接下來的符號表示任意數量的空格換行或制表符(s)但至少要有一個(+)最後的點號和星號 * 僅代表文本的余下部分(對它沒有興趣)

  因此該 regexp 相當於取空格前的第一段文本讓我們來編寫 Java 代碼

  上機實踐

  要在 Java 代碼中使用正則表達式通常需要完成以下七個步驟

  第 創建編譯器實例如果使用 Jakarta Oro則必須實例化 PerlCompiler

  orgapacheorotextregexPerlCompiler compiler =new orgapacheorotextregexPerlCompiler();

  使用 RegexpPlugin 時的等同代碼是相似的

  orgacmslregexppluginCompiler compiler =orgacmslregexppluginRegexpManagercreateCompiler();

  但存在差異正如前面提到的該 API 對實際使用哪個具體實現加以隱藏您可以選擇一個具體實現或保留缺省的 Jakarta Oro如果所選的庫在運行時不可用則 RegexpPlugin API 會嘗試用它的類名創建一個編譯器如果該操作失敗它會將異常發回 API 的客戶機

  假定您一直在使用 JDK 的內置 regexp 類那樣的話包含始終不會使用的額外 jar 文件毫無意義那就是為什麼僅僅調用 createCompiler() 方法還不夠的原因您需要管理這樣的異常每當所選的庫不存在時就會拋出該異常因而必須更新示例

  try

  {

  orgacmslregexppluginCompiler compiler =orgacmslregexppluginRegexpManagercreateCompiler();

  }

  catch (orgacmslregexppluginRegexpEngineNorFoundException exception)

  {

  []

  }

  第 編譯 regexp 模式將正則表達式本身編譯到 Pattern 對象中

  orgapacheorotextregexPattern pattern =pile(*\s*(*?)\s+* PerlCompilerMULTILINE_MASK);

  注您必須轉義反斜槓()字符

  該模式對象代表以文本格式定義的正則表達式請盡可能多地重用模式實例然後如果 regexp 是固定的(缺少任何可變部分(*?)Tom*則模式應是類中的靜態成員

  compile 方法適合用標志(如 EXTENDED_MASK)來配置(請參閱參考資料以獲得更詳細的 regexp 教程)但是RegexpPlugin 並不允許隨意的標志受支持的標志只有 case sensitivity 和 multiline因為所有受支持的庫都可以處理它們

  編譯器實例有特定的特性來定義這些標志

  compilersetMultiline(true);

  orgacmslregexppluginPattern pattern =pile(*\s*(*?)\s+*);

  第 創建 Matcher 對象在 Jakarta Oro 中這一步非常簡單

  orgapacheorotextregexPerlMatcher matcher =new orgapacheorotextregexPerlMatcher();

  它之所以如此簡單是因為它不需要構造任何信息在後來的 regexp 中它將變得具體基本上RegexpPlugin 中的步驟差不多相似您不必親自創建 matcher而是可以將其代理給 RegexpManager 類

  orgacmslregexppluginMatcher matcher =orgacmslregexppluginRegexpManagercreateMatcher();

  區別和前面一樣您需要處理 RegexpEngineNotFoundException實際上RegexpManager 需要為您所選的庫或缺省庫創建 matcher 適配器如果這樣的類在運行時不可用它會拋出該異常

  第 評估正則表達式matcher 對象需要解釋正則表達式並抽取所需的信息這在一行代碼中完成

  if (ntains(John A Smith pattern))

  如果輸入文本與正則表達式匹配則該方法返回 true隱含的副作用是執行該行代碼之後matcher 對象包含在輸入文本中找到的第一個匹配項接下來的一步演示如何實際獲取感興趣的信息

  通過使用 RegexpPlugin API在此時根本沒有任何不同

  第 檢索找到的第一個匹配項這一簡單的步驟僅用一行完成

  orgapacheorotextregexMatchResult matchResult = matchergetMatch();

  您可以聲明一個局部變量來存儲這樣的對象該對象含有與 regexp 匹配的一段文本在這兩種情況下該步驟是相同的除了變量聲明(因為一個是另一個的適配器)

  orgacmslregexppluginMatchResult matchResult =matchergetMatch();

  第 獲取感興趣的 group您可以使用兩種方法

  具體庫

  RegexpPlugin API

  因為您的 regexp 是 *s*(*?)s+*所以您只有一個組(*?)

  MatchResult 對象包含已排序列表中的所有組您只需要知道要獲取的組的位置因為該示例只有一個組所以毫無疑問

  String name = matchResultgroup();

  []

  }

  變量 name 現在包含文本 John那正是您需要的

  第 如果需要則重復該過程如果您需要的信息可多次出現而您想分析所有出現的信息而不只是第一個那麼您只需循環執行第 步到第 直到不滿足第 步中描述的條件為止

  while (ntains(John A Smith pattern)){}

  映射

  除了編寫公共抽象 API主要的工作實際上是實現 Java 環境中某些已存在的 regexp 引擎的適配器

  以下各表提供了對如何從一個庫遷移至另一個庫的詳細描述有些情況中概念明顯不同也有些情況中卻不是那麼明顯

  Regexp 概念 GNU Regexp

  編譯器 gnuregexpRE

  模式 gnuregexpRE

  匹配程序 gnuregexpREMatchEnumeration

  gnuregexpRE

  匹配結果 gnuregexpREMatch

  畸形模式異常 gnuregexpREException

  Regexp 概念 Jakarta Oro

  編譯器 orgapacheorotextregexPerlCompiler

  模式 orgapacheorotextregexPattern

  匹配程序 orgapacheorotextregexPerlMatcher

  匹配結果 orgapacheorotextregexMatchResult

  畸形模式異常 org[]regexMalformedPatternException

  Regexp 概念 Jakarta Regexp

  編譯器 orgapacheregexpRE

  orgapacheregexpRECompiler

  orgapacheregexpREProgram

  模式 orgapacheregexpREProgram

  orgapacheregexpRE

  匹配程序 orgapacheregexpRE

  orgapacheregexpREProgram

  匹配結果 orgapacheregexpRE

  畸形模式異常 orgapacheregexpRESyntaxException

  Regexp 概念 JDK regex 包

  編譯器 javautilregexPattern

  模式 javautilregexPattern

  匹配程序 javautilregexMatcher

  匹配結果 javautilregexMatcher

  畸形模式異常 javautilregexPatternSyntaxException

  基准

  該 API 較顯著的用法之一是用來比較實現測量性能對 Perl 語法的兼容性或其它標准之間的差異

  為這些測試開發的基准實用程序使用 HTML 解析器來處理 Web 內容更新有關鏈接表單和表等元素的信息但是重要的是解析邏輯用正則表達式來表示因此會通過 RegexpPlugin API 實現

  基准測試包括對非常簡單的 HTML 頁面解析 結果在下表中顯示

  Regexp 庫 Benchmark 結果(秒)

  Jakarta Oro

  Jakarta Regexp

  GNU Regexp

  JDK

  您可以用多種方法在實際應用程序中改進性能最重要的是當您使用 regexp 庫時不需要每次都編譯模式而是編譯它們並重用各自的實例但是如果 regexp 本身不固定則不能忽略編譯過程

  因為基准需要在實現之間切換以比較性能所以必須始終廢棄已編譯模式以避免庫之間的交互但是正如您所見大多數已評估的庫有相似的響應時間盡管更詳細的基准能讓我們更好的理解每個庫在不同環境下的行為

  結束語

  正則表達式解析器有強大的功能一旦團隊適應了它解析邏輯就會改進這有助於降低維護但是開發人員需要了解 regexp 語法以理解這些代碼是如何工作的本文已經用一個非常簡單的示例說明了如何使用這些庫中的一個除此之外本文還描述了使用附加層去除客戶機代碼與 regexp 引擎本身之間的耦合的好處


From:http://tw.wingwit.com/Article/program/Java/hx/201311/26533.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.