C/C++字串處理盤點:Char*/String/StringBuilder/TextPool/Rope
許式偉
2008-3-20
概要
在介紹StdExt的時候,我曾經提到,STL設計精良,但是以下幾塊仍然設計不足(或缺失):
- allocator(記憶體管理)
- string(字串處理/文本處理)
- parallel programming(並行編程)
關於記憶體管理,我們已經說得很多了。這裡我們重點談的是字串處理/文本處理相關的問題。本篇是《字串處理完整參考》這個系列的第一篇。
曆史
字串處理/文本處理是一個曆史悠久,並且相當複雜的一個話題。從簡單到字串的比較(compare)、串連(concat),到複雜的文本編輯、Regex、HTML常值內容的解析,都屬於相關的範疇。
在C語言時代,C庫提供了基於char*資料類型的字串處理函數,典型代表如strlen,strcpy,strcat等。原始、容易出錯,是這類字串處理方法的典型特徵。另外,strcat的效率並不高(Borland引入了strecpy來解決這個問題。其實這個strecpy的泛化版本,就是後來STL中的std::copy),而字串尋找(strstr)也是用了最原始的方式。
STL的string(basic_string)的出現,一定程度上改善了這種情況。至少C++程式員有一個使用介面“友善”的string(字串)類了。然而,string類可以說是STL中最受爭議的類(下文我們詳細解釋)。這些爭議至少證明,STL的string類存在設計缺陷。
在SGI STL中,引入了rope類。這是一個重量級的字串類。rope英文本意是繩子。string英文本意是線。所以rope是重量級的string,這個名字取得很形象,非常到位。
在StdExt庫開始考慮字串處理支援的時候,我引入了以下四個類:std::String / std::StringBuilder / std::TextPool / std::Rope。其中,std::String/std::StringBuilder其實是STL string類的功能分拆。std::String是一個常字串,而std::StringBuilder負責字串的修改操作。大家很清楚,String/StringBuilder的概念從Java中引入,我一直認為Java的字串處理類的設計比C++這樣把兩者揉在一起的string實現要合理很多。std::TextPool / std::Rope則是字串類的重量級實現,用來處理巨型的字串。
STL的string(basic_string)的缺陷
歸納起來,STL的string類主要有以下這些爭議點:
- 介面過多且規格和其他STL容器沒有達成很好的一致性。例如,string::find使用下標,而不是以iterator作為迭代位置,這和其他容器不太一樣。
- 記憶體片段。由於過於頻繁的字串構造、析構,導致系統的記憶體片段現象嚴重。
- Copy-On-Write與多安全執行緒。string(basic_string)基於Copy-On-Write技術的原因,是因為 string的賦值被設計成為低開銷的。但是一旦考慮到多安全執行緒問題,Copy-On-Write會把大量的時間花在鎖的開銷上。一些新的STL實現 (如SGI STL)放棄了基於Copy-On-Write的string實現。
盤點StdExt的字串類:String/StringBuilder/TextPool/Rope
為什麼我們需要這麼多的字串類?一個原因:字串處理的應用環境很複雜,需要因地制宜,指望一個string類行遍天下是不可能的。
從支援的串的規模來講,String/StringBuilder重點解決小字串的問題(特別是StringBuilder,在大字串情形下,一定會有效能瓶頸)。而TextPool, Rope重點解決巨型字串的問題。
從實現上來講,String/StringBuilder是線性記憶體的。而TextPool, Rope的字串並不物理連續,它們是邏輯字串。
從支援的操作來講,String是常字串;StringBuilder/TextPool主要支援改寫(set)、添加(append)操作,但不推薦插入(insert)操作,從伸縮性來講,TextPool好要好於StringBuilder;而Rope的操作側重點在於最佳化字串級的複雜操作,如取子字串、插入、刪除等,但是單個字元的修改和擷取代價略高(相比於String/StringBuilder/TextPool)。
後文我們將展開來介紹這些組件。