溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何構(gòu)建用于正則表達式的抽象Java API

發(fā)布時間:2021-12-29 16:13:32 來源:億速云 閱讀:112 作者:小新 欄目:編程語言

這篇文章主要介紹了如何構(gòu)建用于正則表達式的抽象Java API,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。


簡介
盡管您可能認為編寫需要分析文本的 Java 應(yīng)用程序是一項簡單任務(wù),但象許多事情一樣,它會很快變得復(fù)雜起來。那的確是我在編寫代碼以解析 HTML 頁面時的經(jīng)驗。開始的時候,我偶爾會使用 Perl5 正則表達式(regexp)。但是,由于某些原因(稍后說明),我后來常常使用它們。

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

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

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

接受了有 Perl5 經(jīng)驗的開發(fā)人員的建議后,團隊必須選擇哪個 regex 實現(xiàn)最適合他們的項目。然后他們需要學(xué)習(xí)如何使用它。

在簡要地研究了從因特網(wǎng)上找到的眾多可選方案后,假設(shè)團隊決定從人們更熟悉的庫中選擇一個使用,如屬于 Jakarta 項目的 Oro。接下來,對解析器進行較大程度地重構(gòu)或幾乎重新編寫,并且解析器最終使用了 Oro 的類,如 Perl5Compiler、Perl5Matcher 等。

這一決定的后果很明顯:

代碼與 Jakarta Oro 的類緊密地耦合在一起。


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


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


即使庫工作正常,如果他們決定應(yīng)該遷移到全新的庫(例如,包括在 JDK 1.4 中的庫),怎么辦?
去耦的好處
有沒有辦法使團隊知道哪個實現(xiàn)最適合他們的需要呢(不僅現(xiàn)在能將來也能)?讓我們試著尋找答案。

避免依賴任何特定的實現(xiàn)
前面的情形在軟件工程中十分常見。在有些情況中,這樣的情形會導(dǎo)致較大的投資和較長的延期。當不了解所有后果就作出決定而且決策制定人不太走運或缺乏必需的經(jīng)驗時,就常常會發(fā)生這種情況。

可將該情形概括如下:

您需要某種提供者
您沒有選擇最佳提供者的客觀標準
您希望能用最低的成本來評估所有的待選項
所作的決定不應(yīng)將您束縛在所選的提供者上
這一問題的解決方法是使代碼更加獨立于提供者。這引入了新的層 ? 同時去除客戶機和提供者的耦合的層。

服務(wù)器端開發(fā)中,很容易找到使用該方法的模式或體系結(jié)構(gòu)。下面引用一些示例:

對于 J2EE,您主要關(guān)注如何構(gòu)建應(yīng)用程序而不是應(yīng)用程序服務(wù)器的細節(jié)。
數(shù)據(jù)訪問對象(Data Access Object,DAO)模式隱藏了如何訪問數(shù)據(jù)庫(或 LDAP 服務(wù)器、XML 文件等)的細節(jié)和復(fù)雜性,因為它提供了訪問抽象持久存儲層的方法,而您則不需要在客戶機代碼中處理數(shù)據(jù)庫問題(數(shù)據(jù)實際存儲在哪里)。這不是四人組(Gang of Four,GoF)模式,而是 Sun 的 J2EE 最佳實踐的一部分。
在假想的開發(fā)團隊示例中,他們正在尋找這樣的層:

抽象所有正則表達式實現(xiàn)背后的概念。團隊就可以著重學(xué)習(xí)和理解這些概念。他們所學(xué)的可以應(yīng)用到任何實現(xiàn)或版本。


支持新的庫且沒有副作用?;诓寮w系結(jié)構(gòu),動態(tài)選擇執(zhí)行 regexp 模式的實際庫,并且適配器不會被耦合。新庫僅會引入對新適配器的需要。


提供比較不同可選方案的方法。一個簡單的基準實用程序就可以顯示有趣的性能測量結(jié)果。如果對每個實現(xiàn)都執(zhí)行這樣的實用程序,團隊就會獲得有價值的信息并能選擇最好的可選方案。
聽起來不錯,但……
任何去耦方法都至少有一個缺點:如果客戶機代碼僅需要一個實現(xiàn)所提供的特定功能,怎么辦?您不能使用任何其它實現(xiàn),因此您最終將代碼與該實現(xiàn)耦合。也許將來會在這方面有所改善,但您現(xiàn)在卻束手無策。

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

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

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

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

定義一個公共 API
這里講述的 API 名為 RegexpPlugin。已將它設(shè)計成遵循剛剛討論的方法,并且它在 regexp 庫和使用該庫的代碼之間支持去耦。

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

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

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

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

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

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

第 1 步:創(chuàng)建編譯器實例。如果使用 Jakarta Oro,則必須實例化 Perl5Compiler:

org.apache.oro.text.regex.Perl5Compiler compiler =
new org.apache.oro.text.regex.Perl5Compiler();




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

org.acmsl.regexpplugin.Compiler compiler =
org.acmsl.regexpplugin.RegexpManager.createCompiler();




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

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

try
{
org.acmsl.regexpplugin.Compiler compiler =
org.acmsl.regexpplugin.RegexpManager.createCompiler();
}
catch (org.acmsl.regexpplugin.RegexpEngineNorFoundException exception)
{
[..]
}




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

org.apache.oro.text.regex.Pattern pattern =
compiler.compile(".*s*(.*?)s+.*", Perl5Compiler.MULTILINE_MASK);




注:您必須轉(zhuǎn)義反斜杠()字符。

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

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

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

compiler.setMultiline(true);

org.acmsl.regexpplugin.Pattern pattern =
compiler.compile(".*s*(.*?)s+.*");




第 3 步:創(chuàng)建 Matcher 對象。在 Jakarta Oro 中,這一步非常簡單:

org.apache.oro.text.regex.Perl5Matcher matcher =
new org.apache.oro.text.regex.Perl5Matcher();




它之所以如此簡單是因為它不需要構(gòu)造任何信息。在后來的 regexp 中,它將變得具體?;旧?,RegexpPlugin 中的步驟差不多相似。您不必親自創(chuàng)建 matcher,而是可以將其代理給 RegexpManager 類:

org.acmsl.regexpplugin.Matcher matcher =
org.acmsl.regexpplugin.RegexpManager.createMatcher();




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

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

if (matcher.contains("John A. Smith", pattern))
{




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

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

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

org.apache.oro.text.regex.MatchResult matchResult = matcher.getMatch();




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

org.acmsl.regexpplugin.MatchResult matchResult =
matcher.getMatch();




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

具體庫
RegexpPlugin API
因為您的 regexp 是 .*s*(.*?)s+.*,所以您只有一個組:(.*?)

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

String name = matchResult.group(1);

[..]
}




變量 name 現(xiàn)在包含文本 John,那正是您需要的。

第 7 步:如果需要,則重復(fù)該過程。如果您需要的信息可多次出現(xiàn),而您想分析所有出現(xiàn)的信息而不只是第一個,那么您只需循環(huán)執(zhí)行第 5 步到第 7 步,直到不滿足第 4 步中描述的條件為止:

while (matcher.contains("John A. Smith", pattern))
{




映射
除了編寫公共抽象 API,主要的工作實際上是實現(xiàn) Java 環(huán)境中某些已存在的 regexp 引擎的適配器。

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



Regexp 概念 GNU Regexp 1.2
編譯器 gnu.regexp.RE
模式 gnu.regexp.RE
匹配程序 gnu.regexp.REMatchEnumeration
gnu.regexp.RE
匹配結(jié)果 gnu.regexp.REMatch
畸形模式異常 gnu.regexp.REException




Regexp 概念 Jakarta Oro 2.0.6
編譯器 org.apache.oro.text.regex.Perl5Compiler
模式 org.apache.oro.text.regex.Pattern
匹配程序 org.apache.oro.text.regex.Perl5Matcher
匹配結(jié)果 org.apache.oro.text.regex.MatchResult
畸形模式異常 org.[..].regex.MalformedPatternException




Regexp 概念 Jakarta Regexp 1.3
編譯器 org.apache.regexp.RE
org.apache.regexp.RECompiler
org.apache.regexp.REProgram
模式 org.apache.regexp.REProgram
org.apache.regexp.RE
匹配程序 org.apache.regexp.RE
org.apache.regexp.REProgram
匹配結(jié)果 org.apache.regexp.RE
畸形模式異常 org.apache.regexp.RESyntaxException




Regexp 概念 JDK 1.4 regex 包
編譯器 java.util.regex.Pattern
模式 java.util.regex.Pattern
匹配程序 java.util.regex.Matcher
匹配結(jié)果 java.util.regex.Matcher
畸形模式異常 java.util.regex.PatternSyntaxException


基準
該 API 較顯著的用法之一是用來比較實現(xiàn)、測量性能、對 Perl5 語法的兼容性或其它標準之間的差異。

為這些測試開發(fā)的基準實用程序使用 HTML 解析器來處理 Web 內(nèi)容,更新有關(guān)鏈接、表單和表等元素的信息。但是,重要的是解析邏輯用正則表達式來表示,因此會通過 RegexpPlugin API 實現(xiàn)。

基準測試包括對非常簡單的 HTML 頁面解析 10000 次。結(jié)果在下表中顯示。



Regexp 庫 Benchmark 結(jié)果(秒)
Jakarta Oro 2.0.6 130,71
Jakarta Regexp 1.2 23,261
GNU Regexp 1.1.4 1,966.939
JDK1.4 33,222



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

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

感謝你能夠認真閱讀完這篇文章,希望小編分享的“如何構(gòu)建用于正則表達式的抽象Java API”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI