本文系阅读阅读原章节后总结概括得出。由于需要我进行一定的概括提炼,如有不当之处欢迎读者斧正。如果你对内容有任何疑问,欢迎共同交流讨论。
有时候我们需要直接对底层的代码点进行一些操作,而不是处理Character
,这有以下几个原因。
首先,有时候我们真正需要的就是代码点,比如渲染UTF-8编码的网页或者在与非Swift的API交互中用到了代码点。举个例子,我们看一下NSCharacterSet
和Swift中字符串的联合使用。在此前我们说过NSString
使用的是UTF-16编码的代码点,所以如果你想用NSCharacterSet
来分割字符串,你需要在UTF-16视图中进行:
swift copyable">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.utf8
或self.utf32
,代码无法通过编译。
使用代码点而不是字符的另一个原因是处理代码点比字符快得多。这是因为字符需要组合多个代码点,这需要不断向前寻找有没有可以组合的代码点。在后面的“性能”章节中我们会展示这个速度差异。
最后,UTF-16视图还具有一个其他视图不具备的优点:它可以随机访问。String.UFT16View.Index
被拓展,实现了RandomAccessIndexType
协议。在上一节中我们知道字符串在String
类型的内部正是以UTF-16编码方式存储的。随机访问意味着第n个UFT-16的代码点一定在buffer的第n个位置,无论字符串中是否包含非ASCII码。
你可能认为随机访问很少会派上用场,大多数情况下字符串只需要线性访问。但有一些算法依赖于随机访问以保证其效率。比如Boyer- Moore算法(改良版的KMP) 依赖于随机访问,一次跳过多个字符。你也可以在你的算法中利用上这个特性,比如:
swift copyable">// 貌似原文中没有实现search方法,所以这段代码其实无法编译
let greeting = "Hello, world!"
if let idx = greeting.utf16.search("world".utf16)?.samePositionIn(greeting) {
// print(greeting[idx..<greeting.endIndex])
}
复制代码
不过这种效率的提示或简便特性的获得是有代价的,现在你的代码无法确保完全是符合Unicode标准的了,所以下面这个断言将会被触发:
swift copyable">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(数字或字母),所以下面这行代码不会出现问题:
swift copyable">print(text.words())
// 输出结果:
// ["Look", "up", "your", "Pokémon", "in", "a", "Pokédex"]
复制代码