第七章——字串(程式碼點檢視)

bestswifter發表於2017-12-27

本文系閱讀閱讀原章節後總結概括得出。由於需要我進行一定的概括提煉,如有不當之處歡迎讀者斧正。如果你對內容有任何疑問,歡迎共同交流討論。

有時候我們需要直接對底層的程式碼點進行一些操作,而不是處理Character,這有以下幾個原因。

首先,有時候我們真正需要的就是程式碼點,比如渲染UTF-8編碼的網頁或者在與非Swift的API互動中用到了程式碼點。舉個例子,我們看一下NSCharacterSet和Swift中字串的聯合使用。在此前我們說過NSString使用的是UTF-16編碼的程式碼點,所以如果你想用NSCharacterSet來分割字串,你需要在UTF-16檢視中進行:

extension String {
func words(splitBy: NSCharacterSet = .alphanumericCharacterSet()) -> [String] {
return self.utf16.split {
!splitBy.characterIsMember($0)
}.flatMap(String.init)
}
}

let s = "Wow! This contains _all_ kinds of things like 123 and \"quotes\"?"
print(s.words())

// 輸出結果:
// ["Wow", "This", "contains", "all", "kinds", "of", "things", "like", "123", "and", "quotes"]
複製程式碼

具體看一下這段程式碼的原理。它呼叫split方法將字串分割成若干段,分段原理是遇到非數字或字母的字元就分段,分段結果是若干個String.UTF16View的切片,然後再通過flatMap方法轉換成字串,方法的引數是字串的可失敗構造器,這是因為下標有可能落在字元內部的邊界上。因此flatMap方法的使用還有助於我們過濾掉所有nil的元素,這在《可選型別技術之旅》中有詳細描述。

如果你是用的不是self.utf16而是self.utf8self.utf32,程式碼無法通過編譯。

使用程式碼點而不是字元的另一個原因是處理程式碼點比字元快得多。這是因為字元需要組合多個程式碼點,這需要不斷向前尋找有沒有可以組合的程式碼點。在後面的“效能”章節中我們會展示這個速度差異。

最後,UTF-16檢視還具有一個其他檢視不具備的優點:它可以隨機訪問。String.UFT16View.Index被擴充,實現了RandomAccessIndexType協議。在上一節中我們知道字串在String型別的內部正是以UTF-16編碼方式儲存的。隨機訪問意味著第n個UFT-16的程式碼點一定在buffer的第n個位置,無論字串中是否包含非ASCII碼。

你可能認為隨機訪問很少會派上用場,大多數情況下字串只需要線性訪問。但有一些演算法依賴於隨機訪問以保證其效率。比如Boyer- Moore演算法(改良版的KMP) 依賴於隨機訪問,一次跳過多個字元。你也可以在你的演算法中利用上這個特性,比如:

// 貌似原文中沒有實現search方法,所以這段程式碼其實無法編譯
let greeting = "Hello, world!"
if let idx = greeting.utf16.search("world".utf16)?.samePositionIn(greeting) {
//     print(greeting[idx..<greeting.endIndex])
}
複製程式碼

不過這種效率的提示或簡便特性的獲得是有代價的,現在你的程式碼無法確保完全是符合Unicode標準的了,所以下面這個斷言將會被觸發:

let text = "Look up your Pok\u{0065}\u{0301}mon in a Pokédex."
assert(text.utf16.search("Pokémon".utf16) == nil)
複製程式碼

理論上說,字串Pok\u{0065}\u{0301}mon和字串"Pokémon"完全相等,但這裡的search方法會返回nil

Unicode標準把變音符和字母連線起來的字元定義為alphanumeric(數字或字母),所以下面這行程式碼不會出現問題:

print(text.words())

// 輸出結果:
// ["Look", "up", "your", "Pokémon", "in", "a", "Pokédex"]
複製程式碼

相關文章