从协议层设计到实战选型,帮你彻底厘清「什么时候用 Array,什么时候用 Set」。
两个顶层协议:Sequence vs Collection
特性 | Sequence | Collection |
---|---|---|
顺序访问 | ✅ 单向迭代 | ✅ 双向/随机访问 |
可遍历多次 | ❌ 不一定 | ✅ 总是 |
下标访问 | ❌ 无 | ✅ 有 |
count 复杂度 | O(n) | O(1) 或 O(n) |
一句话:所有 Collection 都是 Sequence,反之则不成立。
Sequence:只关心「能不能 for-in」
最小实现
swift
// 斐波那契作为无限序列
struct Fibonacci: Sequence {
func makeIterator() -> some IteratorProtocol {
return FibIterator()
}
}
struct FibIterator: IteratorProtocol {
private var a = 0, b = 1
mutating func next() -> Int? {
let value = a
(a, b) = (b, a + b)
return value
}
}
// 使用
for (i, v) in Fibonacci().prefix(10).enumerated() {
print("F[\(i)] = \(v)")
}
延迟特性(Lazy)
swift
// 不会立即分配 1_000_000 个元素
let huge = (0..<1_000_000).lazy.map { $0 * $0 }
Collection:在 Sequence 上加「下标」
核心层级
markdown
Sequence
└── Collection
├── MutableCollection
├── BidirectionalCollection
└── RandomAccessCollection
自定义 Collection 示例
swift
// 只读字符串切片集合
struct SubstringCollection: Collection {
let base: String
let substrings: [Substring]
var startIndex: Int { 0 }
var endIndex: Int { substrings.count }
func index(after i: Int) -> Int { i + 1 }
subscript(position: Int) -> Substring { substrings[position] }
}
四大常用容器对比
容器 | 有序? | 唯一? | 读写 | 典型场景 |
---|---|---|---|---|
Array | ✅ | ❌ | 随机读写 | 列表、栈、队列 |
Set | ❌ | ✅ | Hash 表 | 去重、交集、差集 |
Dictionary | ❌ | Key 唯一 | 键值映射 | 缓存、配置 |
NSOrderedSet | ✅ | ✅ | 索引+唯一 | 有序去重(需 Foundation) |
Array 深度指南
值语义与写时复制 (CoW)
swift
var a = [1, 2, 3]
var b = a // 共享同一块内存
b.append(4) // 触发 CoW,复制一份
性能陷阱:大数组拷贝
swift
// 避免在 for 循环中直接 append
let squared = largeArray.map { $0 * $0 } // 一次分配
Set:哈希与唯一性的艺术
基本操作
swift
let odd: Set = [1, 3, 5]
let even: Set = [2, 4, 6]
print(odd.union(even)) // [1, 2, 3, 4, 5, 6]
print(odd.intersection(even)) // []
自定义可哈希类型
swift
struct Point: Hashable {
let x, y: Int
// 自动合成 Hashable
}
let points: Set<Point> = [Point(x: 1, y: 2), Point(x: 3, y: 4)]
Dictionary:字典的正确打开方式
分组 & 计数
swift
let words = ["apple", "banana", "apricot"]
let grouped = Dictionary(grouping: words, by: { $0.first! })
// ["a": ["apple", "apricot"], "b": ["banana"]]
let counts = Dictionary(words.map { ($0, 1) }, uniquingKeysWith: +)
嵌套字典优雅读写
swift
extension Dictionary where Key == String, Value == Any {
subscript(path path: String) -> Any? {
get {
var keys = path.split(separator: ".").map(String.init)
return keys.reduce(self) { ($0 as? [String: Any])?[$1] }
}
}
}
let dict = ["user": ["profile": ["name": "Ada"]]]
print(dict[path: "user.profile.name"] ?? "N/A") // "Ada"
选型决策树
javascript
需要下标随机访问?
├─ 是 → Collection
│ ├─ 需要唯一性 → Set / Dictionary
│ └─ 需要顺序 → Array
└─ 否 → Sequence(延迟计算,节省内存)
性能速查表
操作 | Array | Set | Dictionary |
---|---|---|---|
append/insert | O(1) amortized | O(1) | O(1) |
contains | O(n) | O(1) | O(1) |
remove | O(n) | O(1) | O(1) |
sort | O(n log n) | ❌ 无序 | ❌ 无序 |
实战 Checklist
- 列表渲染 →
Array
- 搜索去重 →
Set
- 键值缓存 →
Dictionary
- 大数据链式处理 →
.lazy
Sequence - 需要有序去重 →
NSOrderedSet
(Foundation)
一句话总结
顺序访问用 Array,唯一性用 Set,键值映射用 Dictionary,延迟计算用 Sequence。
牢记这句口诀,Swift 数据结构选型不再纠结!