在Swift中获取第n个字符:如何不降低性能

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

Swift中字符串的魔力:为何“获取某个字符”成了一道难题

\n\n

任何从Python、JavaScript或C++转到Swift的开发者,最终都会遇到一道坎。看似简单的任务:“给我字符串的第三个字符。”在Python中是str[2],在JavaScript中是str[2],但在Swift中……却要复杂得多。如果你曾搜索过“Swift中获取字符串第n个字符”的答案,就会知道Stack Overflow上简直泛滥着这个问题(成千上万的浏览量,数百个赞)。

\n\n

为什么呢?因为Swift不把字符串当作字节数组,而是当作一种复杂的、符合Unicode标准的字素序列。如果直接处理这项任务——性能会下降,代码也会变得难以阅读。

\n\n

在本文中,我们不仅会给出答案。我们将深入剖析优化Swift中第n个字符的获取,考察不同方法的性能,并选出最佳方案。开始吧!

\n\n

为什么string[2]行不通?String.Index快速入门

\n\n

在Swift中,String不是一个支持随机访问的Character集合。它是一个通过索引访问的集合,而这些索引不是整数。为什么呢?

\n\n
    \n
  • Unicode和字素。一个“字符”可能由多个Unicode标量组成(例如,表情符号“🇷🇺”或带重音的“é”)。如果索引只是简单的Int,那么str[1]可能只返回半个表情符号。
  • \n
  • 性能。将索引存储为Int对于Unicode字符串来说是一种妥协。Swift对开头和结尾的访问是O(1)的,但对于任意索引,你需要遍历字符串。
  • \n
\n\n

因此,在Swift中获取第n个字符的标准方法如下:

\n\n
let str = "Привет, Swift!"\nlet index = str.index(str.startIndex, offsetBy: 2)\nlet char = str[index] // "и"
\n\n

这能工作吗?能。是最优的吗?不是。

\n\n

性能问题:offsetBy隐藏了什么

\n\n

每次调用str.index(startIndex, offsetBy: n)都是一个O(n)操作。想象一个10000个字符的字符串,你想获取第9999个字符。Swift需要遍历所有9999个字符才能找到正确的那个。在一个循环中做100次?你得到的是平方复杂度。

\n\n

下面是一个初学者常写的低效代码示例:

\n\n
let longString = "A" + String(repeating: "b", count: 10_000) + "C"\nfor i in 0..<longString.count {\n    let index = longString.index(longString.startIndex, offsetBy: i)\n    let char = longString[index]\n    // 对char进行操作\n}
\n\n

这段代码运行起来会非常慢,因为每次offsetBy调用都从startIndex开始。实际复杂度是O(n²)。

\n\n

优化方案 #1:使用递增索引

\n\n

加速Swift获取第n个字符的最简单方法,就是不要每次都从头开始重新计算索引。使用index(after:)或formIndex(after:)。

\n\n
let longString = "A" + String(repeating: "b", count: 10_000) + "C"\nvar index = longString.startIndex\nfor _ in 0..<longString.count {\n    let char = longString[index]\n    // 对char进行操作\n    index = longString.index(after: index) // O(1)!\n}
\n\n

现在复杂度是线性的O(n)。但如果我们需要按索引随机访问,而不是顺序访问呢?

\n\n

优化方案 #2:转换为字符数组(附带说明)

\n\n

如果你需要频繁地按任意索引访问字符(第n个字符),且字符串不是特别大,最简单的方法是将其转换为[Character]。这样访问就是O(1)了。

\n\n
let str = "Привет, Swift!"\nlet chars = Array(str)\nlet char = chars[2] // "и

博客

书籍推荐