Swift 字符串与字符完全导读(三):比较、正则、性能与跨平台实战

字符串比较的 3 个层次

比较方式 API 等价准则 复杂度 备注
字符相等 "==" 扩展字形簇 canonically equivalent O(n) 最常用
前缀 hasPrefix(:) UTF-8 字节逐段比较 O(m) m=前缀长度
后缀 hasSuffix(:) 同上,从后往前 O(m) 注意字形簇边界

示例

swift 复制代码
let precomposed = "café"                    // U+00E9
let decomposed  = "cafe\u{301}"             // e + ́
print(precomposed == decomposed)            // true ✅ 字形簇等价

let aEnglish = "A"  // U+0041
let aRussian = "А"  // U+0410 Cyrillic
print(aEnglish == aRussian)                 // false ❌ 视觉欺骗

Unicode 正规化(Normalization)

有时需要把"视觉上一样"的字符串统一到同一二进制形式,再做哈希或数据库唯一索引。

Swift 借助 Foundation 的 decomposedStringWithCanonicalMapping / precomposedStringWithCanonicalMapping:

swift 复制代码
import Foundation

func normalized(_ s: String) -> String {
    s.decomposedStringWithCanonicalMapping
}

let set: Set<String> = [
    normalized("café"),
    normalized("cafe\u{301}")
]
print(set.count) // 1 ✅ 去重成功

Swift 5.7+ Regex 一站式入门

字面量构建

swift 复制代码
import RegexBuilder

let mdLink = Regex {
    "["                               // 字面左括号
    Capture { OneOrMore(.any) }       // 链接文字
    "]("
    Capture { OneOrMore(.any) }       // URL
    ")"
}

let text = "见 [官方文档](https://swift.org)。"
if let match = text.firstMatch(of: mdLink) {
    let (whole, title, url) = match.output
    print("文字:\(title)  地址:\(url)")
}

性能提示

  • 字面量 Regex 在编译期构建,零运行时解析成本;
  • 捕获组数量 < 5 时,使用静态 Output 类型,无堆分配。

切片 + 区间:一次遍历提取所有信息

需求:把 "/api/v1/users/9527" 拆成版本号与 ID

swift 复制代码
let path = "/api/v1/users/9527"
// 1. 找到两个数字区间
let versionRange = path.firstRange(of: /v\d+/)!          // Swift 5.7  Regex 作为区间
let idRange      = path.firstRange(of: /\d+$/)!

// 2. 切片
let version = path[versionRange]  // "v1"
let userID  = path[idRange]       // "9527"

关键点:

  • path[range] 返回 Substring,长期存需 String(...)。
  • 正则区间可链式调用,避免多次扫描。

性能 Benchmark(M4 MacBook Pro, Release 构建)

测试 1:100 万次 "==" 比较

swift 复制代码
import QuartzCore
func measure(action: () -> Void) {
    let startTimeinterval = CACurrentMediaTime()
    action()
    let endTimeinterval = CACurrentMediaTime()
    print((endTimeinterval - startTimeinterval) * 1_000)
}

let s1 = "Swift字符串性能测试"
let s2 = "Swift字符串性能测试"

measure { for _ in 0..<1_000_000 { _ = s1 == s2 } }
//  耗时  0.0025 ms

测试 2:100 万次 hasPrefix

swift 复制代码
measure { for _ in 0..<1_000_000 { _ = s1.hasPrefix("Swift") } }
//  耗时  76 ms

测试 3:提取 Markdown 链接 10 万次

swift 复制代码
let blog = String(repeating: "见 [官方文档](https://swift.org)。\n", count: 10_000)
measure { _ = blog.matches(of: mdLink).map { $0.output } }
//  median  12 ms

结论:

  • 比较操作已高度优化,可放心用于字典 Key;
  • 正则采用静态构建后,与手写 Scanner 差距 < 5%。

常见"坑"与诊断工具

场景 现象 工具/修复
Substring 泄漏 百万行日志内存暴涨 Instruments → Allocations → 查看 "Swift String" 的 CoW 备份
整数下标越界 运行时 crash 使用 index(_, offsetBy:, limitedBy:) 安全版
正则回溯爆炸 卡住 100% CPU 在 Regex 内使用 Possessive 量词或 OneOrMore(..., .eager)
比较失败 "é" != "é" 检查是否混入 Cyrillic / Greek 等视觉同形字符;打印 unicodeScalars 调试

终极最佳实践清单

  1. 比较:优先用 "==",必要时先正规化再哈希。
  2. 前缀/后缀:用 hasPrefix / hasSuffix,别手写 prefix() 再比较。
  3. 索引:永远通过 String.Index 计算,禁止 str[Int]。
  4. 子串:函数返回前立即 String(substring),防止隐式内存泄漏。
  5. 拼接:大量小字符串先用 [String] 收集,最后 joined();或 reserveCapacity 预分配。
  6. 正则:静态字面量 Regex 性能最佳;捕获组能少就少。
  7. 遍历:
    • 看"人眼字符"→ for ch in string
    • 看"UTF-8 字节"→ string.utf8
    • 看"Unicode 标量"→ string.unicodeScalars
  8. 多线程:String 是值类型,跨线程传递无数据竞争,但共享大字符串时 Substring 会拖住原内存,及时转存。
  9. 日志 / 模板:多行字面量 + 插值最清晰;需要原始反斜杠用扩展分隔符 #"..."#。
  10. 性能测量:用 swift test -c release + measure 块, Instruments 只看 "Swift String" 的 CoW 备份次数。
相关推荐
茶底世界之下5 天前
为什么预览、录制、离线导出不能共享同一个背压策略?
ios·swift
2501_915909065 天前
移动端开发工具链怎么组合比较好,iOS、Android、跨端三套配置方案
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
东坡肘子5 天前
一场关于 SwiftUI 动画的“原生”之争 -- 肘子的 Swift 周报 #154
人工智能·swiftui·swift
2501_915918415 天前
iOS编程用什么软件好?主流工具Xcode、AppCode、CodeRunner与新兴快蝎对比
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
9765033356 天前
iOS 上架 4.3a 被拒【uniapp专讲】
flutter·ios·objective-c·uniapp·swift
传奇开心果编程7 天前
【SwiftUI入门练中学】第1课 从零开始
学习·ui·ios·swiftui·swift
丿Wayne7 天前
iOS Token 刷新实战(Actor 避免并发重复刷新)
ios·swift·token·actor·swift并发
大熊猫侯佩8 天前
独立 App 首发完成 iPhone Duo 展开态适配
ios·swiftui·swift
SoaringHeart8 天前
Swift 进阶&封装:仿 Get 给SwiftUI 添加路由导航能力
swiftui·swift
2501_915918419 天前
使用 VS Code 写 Swift 做 iOS 开发,插件能实现哪些功能,还有哪些短板
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程