為了改進這種情況,Matcher應(yīng)該而且能夠利用 Rope迭代器提供的更快的 O(1)訪問時間。為了掌握這種方法,首先需要理解 Matcher到其 CharSequence的訪問模式。
正則表達式匹配常見的場景是從字符序列的某個點上開始,向前移動,直到找到所有匹配,并到達序列末尾為止。在這個場景中,匹配器主要是向前移動,一次通常移動不止一個字符。但在少數(shù)情況下,匹配器被迫向后移動。
很容易能將 Rope迭代器改成一次向前移動不止一個字符。但是向后移動要復(fù)雜些,因為迭代器內(nèi)部執(zhí)行的是深度優(yōu)先算法,預(yù)先排好了 Rope遍歷的順序,需要訪問每個葉節(jié)點。遍歷堆棧沒有足夠的信息能夠前移到前一個葉節(jié)點,但是如果促使向后移動的信息量沒有使迭代器離開當(dāng)前葉節(jié)點,那么迭代器有可能服務(wù)請求。為了說明這個作法,圖 5 顯示一個虛構(gòu)的迭代器的狀態(tài),它能夠向后移動一、二、三、四個位置,但不能移動更多位置,因為這要求訪問前面訪問的葉節(jié)點。
為了支持這一新功能,可以修改 Rope的 charAt方法,這樣在第一次調(diào)用的時候,在指定位置上構(gòu)建一個迭代器。后續(xù)的調(diào)用會將迭代器前后移動指定的距離。如果迭代器不能后移指定的距離,那么執(zhí)行默認的 charAt例程取得字符的值 —這種情況很少發(fā)生。
因為這種優(yōu)化無法做到普遍適用,而且要求引入新的成員變量,所以好不要直接將它添加到 Rope類。相反,可以根據(jù)需要用這個優(yōu)化修飾 rope。為此,Ropes for Java 在 Rope類中包含了一個方法,能夠為指定的模式生成優(yōu)化的匹配器。清單 5 演示了這種方法:
清單 5. 優(yōu)化的正則表達式匹配
Pattern p = ...
Matcher m = rope.matcher(p);
清單 5 中第二行調(diào)用對 rope 進行修飾,優(yōu)化正則表達式匹配。
表 7 提供了這種方法的測評結(jié)果,并包含了以前的結(jié)果(表 6)以便進行對照。
技術(shù) | 時間(單位:納秒) |
---|---|
String |
75,286,078 |
StringBuffer |
86,083,830 |
Rope |
12,507,367,218 |
重新均衡后的 Rope |
2,628,889,679 |
Rope.matcher |
246,633,828 |
優(yōu)化的結(jié)果比起重新均衡后的 rope 明顯提高了 10.6 倍,使 rope 的性能與 String性能的差距縮小到 3.2 倍之內(nèi)。
應(yīng)用程序
什么時候不應(yīng)使用 rope企業(yè)級 Java 應(yīng)用程序經(jīng)常包含類似下面的代碼:
String x = "<input type='text' name='name' value='"
+ escapePcData(bean.getName()) + "'>";
x 隨后放在 HTML 內(nèi)發(fā)送到客戶機瀏覽器。用 rope 代替編譯器默認生成的 StringBuilder來計算 x 的值是否有意義?
回答是否,原因有幾個。首先,這里要連接的數(shù)據(jù)的數(shù)量比較小,所以使用 rope 不會提高性能(雖然能夠提高健壯性和伸縮性)。(請設(shè)想一下 getName出人意料地返回 50 MB 字符串時這兩種解決方案會如何反應(yīng)。)
但是出于討論的目的,假設(shè)有許多塊數(shù)據(jù)進行連接。由于 Rope的附加性能通常比 StringBuffer好,這時使用 rope 是否有意義呢?答案還是否。不論何時將輸入的數(shù)據(jù)組合在一起形成格式化輸出時,漂亮有效的方法是使用模板引擎(例如 StringTemplate 或 FreeMarker)。這種方法不僅能干凈地將表示標(biāo)記與代碼分開,而且模板只進行一次編譯(通常編譯為 JVM 字節(jié)碼),以后可以重用,從而使它們擁有的性能特征。
使用模板的第二個好處暴露了對于類似以上代碼中那些輸出構(gòu)建例程(包括用 rope 編寫的例程)常見的基本缺陷。這個好處是:可以對模板進行逐步評估,而且輸出一旦生成可以寫入 Writer,不必先在內(nèi)存中累積。在 Java EE 應(yīng)用程序中,Writer實際是到客戶機瀏覽器的緩沖的連接,這種輸出方法呈現(xiàn)恒定的內(nèi)存量 —— O(1),而其他解決方案的內(nèi)存使用則是 O(n)。這對應(yīng)用程序的可伸縮性和健壯性都是 巨大的改進,雖然對小的輸出或較低的應(yīng)用程序負載來說不是那么明顯。(請參閱 參考資料中兩篇關(guān)于流式架構(gòu)的文章的鏈接,獲得進一步解釋和定量分析。)
.現(xiàn)在對于 rope 的性能已經(jīng)有了很好的理解,可以考慮 rope 的一些傳統(tǒng)用法,以及在 Java EE 應(yīng)用程序中吸引人但很可能 并不恰當(dāng)?shù)挠梅ā?/p>
雖然 rope 可以作為一種通用方法替代字符串的連續(xù)內(nèi)存表示法,但是只有在大量修改大型字符串的應(yīng)用程序中才能看到明顯的性能提升。這可能并不讓人驚訝,因為早的 rope 應(yīng)用程序是用來在文本編輯器中表示文檔。不僅在特大的文檔中能夠以幾乎恒定的時間執(zhí)行文本插入和刪除,rope 的不可修改性還使得 “撤消堆棧(undo stack)” 的實現(xiàn)非常容易:只要在每次修改時保存對前一個 rope 的引用即可。
另一個更加神奇的 rope 應(yīng)用是表示虛擬機的狀態(tài)。例如,ICFP 2007 編程競賽中有一個比賽是實現(xiàn)一個虛擬機,要求每個周期都修改它的狀態(tài),并針對某些輸入運行數(shù)百萬個周期(請參閱 參考資料)。在一個 Java 實現(xiàn)中,虛擬機的速度提高了三個數(shù)量級,從 ~50 周期 / 秒提高到超過 50,000/ 秒,這是通過使用 Rope代替專門的 StringBuffer來表示狀態(tài)而做到的。
未來的研究方向
雖然 Ropes for Java 是一種新庫,但底層概念并不新鮮,該庫看起來實現(xiàn)了 rope 的性能許諾。但是,該項目希望通過以下方面在未來的發(fā)行版中對庫的某些方面進行改進:
•提供其他常見字符串操作的高性能實現(xiàn)。
•編寫適配器,將 rope 無縫地集成到 Scala和面向 Java 平臺的其他高級語言。
•通過進一步的自動測試提高質(zhì)量。Ropes for Java 既使用手工編寫的 JUnit 自動測試進行了測試,也通過 JUnit 工廠自動生成的測試進行了測試。集成 ESC/Java 2檢驗過的 Java 建模語言(JML)標(biāo)注可能會進一步提高質(zhì)量。