+ 我要发布
我发布的 我的标签 发现
浏览器扩展
斑点象@Edge

Swift 字符串的一些隐藏知识

Swift 中的 String 和 NSString 是可以用 as 无缝转换的,但是 String 的 count 属性和 NSString 的 length 的值可能不同。 例如 ``` let swiftString = "👨‍👩‍👧‍👦" let nsString = swiftString as NSString print(swiftString.count) // 打印 1 print(nsString.length) // 打印 11 ``` 同样是一个 emoji 符号,Swift String 的 count 打印 1,但是转成 NSString 使用 length 打印 11。 这是因为,count 属性是测量的屏幕上显示的字符数,这里的 emoji 在屏幕上只显示一个字符,所以值是 1。 而 NSString 是使用 UTF-16 编码的,它的 length 属性实际上是 UTF-16 编码的长度。 关于 Unicode 编码 前面提到了 Unicode 编码,很多程序员都搞不明白,这里顺带提一句,Unicode 是一种计算机文本编码标准,简单来说就是让字符显示在屏幕上的。 在 Unicode 编码出现之前,世界上还有很多编码标准,比如 ASCII、ISO 8859-1 等,编码方式不统一,解码的时候就会出现乱码的情况,后来就设计了 Unicode 编码,大家用同一套标准,这样就不会乱码了。 上边说到的 UTF-16 其实是 Unicode 的一种编码方案,为了在内存使用、兼容性和编码效率之间取得平衡,Unicode 标准定义了 UTF-8、UTF-16、UTF-32 几种不同的编码方案,iOS 内部使用的就是 UTF-16。 其实 swift 提供了几种编码的转换函数,可以自由切换: ``` let swiftString = "👨‍👩‍👧‍👦" print(swiftString.utf8.count) // utf8 打印 25 print(swiftString.utf16.count) // utf16 打印 11 print(swiftString.unicodeScalars.count) // utf32 打印 7 ``` 检查字符串 之前看到一些开源项目在判断空字符串时使用 string.count == 0,每当看到这样的写法,我都会顺手提个 PR 改成 string.isEmpty。 相比之下,用 string.isEmpty 来判断空字符串的性能更好,因为在底层实现中 count 属性需要遍历字符串的元素。 系统对字符串的性能优化 写入时复制(Copy-on-Write) 我们都知道 Swift 的字符串是值类型的,这意味着每个字符串变量都拥有独立的数据副本。原本每次读写都会产生一份新的副本,但这会产生较高的性能开销。 为了减少性能开销,Swift 引入了一种称为“写入时复制”(Copy-on-Write, COW)的策略来优化这一过程。 具体来说,当你复制一个字符串时,Swift 并不立即复制字符串的数据,而是让新旧字符串实例共享相同的的数据缓冲区。只有当你尝试修改其中一个字符串时,Swift 才会进行实际的数据复制操作。也就是在只有真正需要时才会发生数据的复制,从而减少了不必要的性能开销。 这种机制带来一个后果,如果你有多个字符串实例共享同一个数据缓冲区,那么对其中一个字符串进行修改的操作可能会导致 O(n) 的时间和空间开销,因为需要复制整个数据缓冲区。这里的 n 指的是字符串的长度。 缓冲区的指数增长策略 Swift 的字符串还采用了一种缓冲区的指数增长策略来优化字符串的追加操作。当你向字符串追加内容时,如果当前的数据缓冲区已满,Swift 会分配一个新的更大的缓冲区,并将现有数据复制到这个新缓冲区中。 这个新缓冲区的大小并不是简单地加上新增数据的大小,而是按照一定的比例(通常是翻倍)增加,以便为未来的追加操作留出空间。这种策略的好处是,虽然某次特定的追加操作可能需要重新分配缓冲区并复制数据(这是一次较为昂贵的操作),但随着缓冲区大小的增长,这种情况发生的频率会逐渐减少。在对大量追加操作进行平均时,每次追加操作的平均时间成本会趋近于常数(O(1)),从而提高了追加操作的整体性能。
我的笔记