为何 String.CharacterView 不是 MutableCollection

作者:Ole Begemann,原文链接,原文日期:2017-02-07
译者:Cwift;校对:walkingway;定稿:CMB

本系列的其他文章:
(1)Dictionary and Set
(2)String.CharacterView(本文)

上一篇文章 中,我讨论了为什么 Set 和 Dictionary 不能遵守 MutableCollection 和 RangeReplaceableCollection。今天轮到 String.CharacterView 了。

CharacterView 遵守了 RangeReplaceableCollection,但不能遵守 MutableCollection。这是为什么呢?字符串明显是可变的; 从逻辑来看它应该遵守 MutableCollection 协议。我们需要再次回过头来考虑下协议的语义

MutableCollection 的文档制定了以下要求:

MutableCollection 协议允许更改集合中元素的值,但不允许更改集合本身的长度...
在 MutableCollection 实例的某个下标位置保存的值,之后必须可以在同一位置访问。也就是说,对于可变集合实例 a,索引 i 和值 x,以下代码示例中的两组赋值必须是等价的:

a[i] = x
let y = a[i]
// 必须等同于
a[i] = x
let y = x

替换字符可能使索引失效

用 CharacterView 尝试一下。开始有一个初始字符串 a 和一个指向 “_” 字符的索引 i ,然后用一个表情符号(新值 x)来替换它:

var a = "Grinning face: _".characters
let i = a.index(of: "_")!
// 校验 a[i]
a[i] // → "_"

let x: Character = "😀"

a[i] = x 不能理解成替换,因为缺少了 MutableCollection 协议中的一致性,但是可以用 replaceSubrange(_:with:) 来实现该行为:

//a[i] = x 的解决方案
a.replaceSubrange(i...i, with: CollectionOfOne(x))

// 替换的工作过程:
String(a) // → "Grinning face: 😀"
// 现在 a[i] 应该仍旧返回 "_":
a[i] // → "�" 错误!

a[i] 返回了 � (Unicode 的替换字符 U + FFFD),这是错误的标志。replaceSubrange 方法执行了,但是索引 i 不再有效。用 dump 函数来查看它的底层结构,就能知道上例的原因:

let newIndex = a.index(of: "😀")!
dump(newIndex)
/* →
▿ Swift.String.CharacterView.Index
  ▿ _base: Swift.String.UnicodeScalarView.Index
    - _position: 15
  - _countUTF16: 2
*/

可以看到,单个字符的突变会导致该字符的索引无效。String.CharacterView 违反了 MutableCollection 协议的语义,所以不能遵守它。RangeReplaceableCollection 的语义不同,所以 CharacterView 可以遵守它。

字符具有可变长度的编码

观察一下 MutableCollection 的另一个准则,即突变不能影响集合的总长度。CharacterView 可以满足这个要求:一个字符的长度总是 1,所以字符间的替换可以保持字符串的总长度不变。但是,在底层存储中,对每一个字符来说,字符本身的尺寸不同的。因此替换单个字符可能会使后续文本在内存中向前或向后移动几个字节,从而为本次替换操作留足空间。这使得简单的下标赋值操作是潜在的 O(n) 复杂度操作,而下标取值为 O(1) 复杂度。以下引用来自 Collection 的文档

遵守 Collection 协议的类型应该提供 startIndex 和 endIndex 属性,并为元素提供 O(1) 复杂度的下标访问。不能保证预期性能的类型应做出标注,因为许多集合的高效操作依赖于 O(1) 复杂度的下标操作。

这里只谈到了下标的 getter 方法,并且 MutableCollection 的文档中没有提到任何有关 setter 方法的预期性能,不过假设它与 Collection 具有相同的特性应该是合理的。

Unicode 的副作用

响应 CharacterView 遵守 MutableCollection 的最终潜在问题是 Unicode 和它带来的复杂性。组合字符的存在意味着,替换单个字符时如果新字符与其之前的字符发生了组合,替换单个字符实际上可以改变字符串的长度(以字符度量)。

在以下示例中,我们将“1_”中的下划线替换为 U + 20E3 COMBINING ENCLOSING KEYCAP,然后将其与之前的字符组合:

var s = "1_".characters
s.count // → 2,意料之中
let idx = s.index(of: "_")!

// U+20E3 COMBINING ENCLOSING KEYCAP
let keycap: Character = "\u{20E3}"

// 用 keycap 替换 _
s.replaceSubrange(idx...idx, with: [keycap])

// keycap 和它前面的字符组合起来了
String(s) // → "1⃣"
//长度变成 1 了
s.count // → 1

结果字符串 “1⃣” 只有1个字符长,再次违反了 MutableCollection 的语义。 并且访问 s[idx] 将发生崩溃,因为索引指向了不再存在的位置。可以说,这个例子的主要观点并不在于 CharacterView 是否违反 MutableCollection 的语义,而是 Unicode 是复杂的并且容易被滥用 —— 你通常不应该把组合字符中的字符拆开来用。仅仅一些违背协议语义的晦涩的 Unicode 副作用,不足以为成为 CharacterView 违反该协议的理由。Unicode 是如此的复杂,以至于它的特性不可能完全满足泛型的集合协议的约束。

因此,String 可能会在 Swift 4 中再次成为一个集合,同时承认一些操作可能会出现违背严格的集合语义的“退化”情况。

正如我们所看到的,Unicode 在这种情况下不是决定因素。 还有其他一些东西,使 CharacterView 与 MutableCollection 语义不兼容。

本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问 http://swift.gg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容