如何構(gòu)建用于正則表達(dá)式的抽象JavaAPI

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

成都創(chuàng)新互聯(lián)專注于網(wǎng)站建設(shè)|企業(yè)網(wǎng)站維護(hù)|優(yōu)化|托管以及網(wǎng)絡(luò)推廣,積累了大量的網(wǎng)站設(shè)計(jì)與制作經(jīng)驗(yàn),為許多企業(yè)提供了網(wǎng)站定制設(shè)計(jì)服務(wù),案例作品覆蓋發(fā)電機(jī)回收等行業(yè)。能根據(jù)企業(yè)所處的行業(yè)與銷售的產(chǎn)品,結(jié)合品牌形象的塑造,量身開發(fā)品質(zhì)網(wǎng)站。


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

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

由于邏輯變得更加復(fù)雜,所以維護(hù)也變得很困難。因?yàn)槿魏胃亩伎赡墚a(chǎn)生副作用并使文本解析器的其它部分停止工作,所以開發(fā)人員需要時(shí)間修正這些小錯(cuò)誤。

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

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

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

這一決定的后果很明顯:

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


團(tuán)隊(duì)承擔(dān)了風(fēng)險(xiǎn),因?yàn)椴恢婪枪δ苄孕枨螅ㄈ缧阅芑蚓€程模型)是否將得到滿足。


團(tuán)隊(duì)已花費(fèi)時(shí)間和財(cái)力來學(xué)習(xí)并重新編寫代碼,以使它使用 regexp 庫。如果他們的決定是錯(cuò)誤的并且選擇了新的庫,則這一工作在成本上將不會有很大區(qū)別,因?yàn)閷⑿枰俅沃匦戮帉懘a。


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

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

可將該情形概括如下:

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

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

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

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


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


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

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

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

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

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

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

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

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

第一部分是點(diǎn)號和星號字符 .*,它們在這里表示任意數(shù)量的空格和 (.*?) 組之前的任何字符。第二部分比較引人注意(因?yàn)樗粓A括號括起來)。問號表示取第一個(gè)符合條件的項(xiàng)。

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

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

上機(jī)實(shí)踐
要在 Java 代碼中使用正則表達(dá)式,通常需要完成以下七個(gè)步驟:

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

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




使用 RegexpPlugin 時(shí)的等同代碼是相似的:

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




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

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

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




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

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




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

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

compile 方法適合用標(biāo)志(如 EXTENDED_MASK)來配置(請參閱參考資料以獲得更詳細(xì)的 regexp 教程)。但是,RegexpPlugin 并不允許隨意的標(biāo)志。受支持的標(biāo)志只有 case sensitivity 和 multiline,因?yàn)樗惺苤С值膸於伎梢蕴幚硭鼈儭?br/>
編譯器實(shí)例有特定的特性來定義這些標(biāo)志:

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();




它之所以如此簡單是因?yàn)樗恍枰獦?gòu)造任何信息。在后來的 regexp 中,它將變得具體。基本上,RegexpPlugin 中的步驟差不多相似。您不必親自創(chuàng)建 matcher,而是可以將其代理給 RegexpManager 類:

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




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

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

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




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

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

第 5 步:檢索找到的第一個(gè)匹配項(xiàng)。這一簡單的步驟僅用一行完成:

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




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

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




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

具體庫
RegexpPlugin API
因?yàn)槟?regexp 是 .*s*(.*?)s+.*,所以您只有一個(gè)組:(.*?)

MatchResult 對象包含已排序列表中的所有組。您只需要知道要獲取的組的位置。因?yàn)樵撌纠挥幸粋€(gè)組,所以毫無疑問:

String name = matchResult.group(1);

[..]
}




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

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

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




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

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



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


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

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

基準(zhǔ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



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

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

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

分享文章:如何構(gòu)建用于正則表達(dá)式的抽象JavaAPI
轉(zhuǎn)載注明:http://muchs.cn/article16/ihsogg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信小程序靜態(tài)網(wǎng)站、虛擬主機(jī)、搜索引擎優(yōu)化定制網(wǎng)站、App設(shè)計(jì)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護(hù)公司