Swift集合类型(Array、Dictionary、Set)源码分析
一、Swift集合类型概述
1.1 集合类型的定义与分类
Swift的集合类型是构建在泛型和协议之上的核心数据结构,其设计遵循了"以协议为中心"的编程范式。从源码角度看,所有集合类型都直接或间接遵循了Sequence
协议,该协议定义了集合的基本迭代能力:
public protocol Sequence {
associatedtype Element
associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
func makeIterator() -> Iterator
}
在Sequence
之上,Collection
协议扩展了更高级的功能,如索引访问和非破坏性迭代:
public protocol Collection: Sequence {
associatedtype Index: Comparable
associatedtype SubSequence: Collection where SubSequence.Element == Element
var startIndex: Index { get }
var endIndex: Index { get }
subscript(position: Index) -> Element { get }
func index(after i: Index) -> Index
}
Swift标准库中的三大集合类型Array
、Dictionary
和Set
分别实现了这些协议,并根据自身特性进行了扩展。例如,Array
实现了RandomAccessCollection
协议,支持高效的随机访问:
public struct Array<Element> {
// 内部存储结构
@usableFromInline
internal var _buffer: _ArrayBuffer<Element>
}
extension Array: RandomAccessCollection {
public typealias Index = Int
public typealias Indices = CountableRange<Int>
public var startIndex: Int { return 0 }
public var endIndex: Int { return count }
public subscript(position: Int) -> Element {
_precondition(position < count, "Index out of range")
return _buffer.withUnsafeMutablePointerToElements { $0[position] }
}
}
1.2 集合类型的核心价值
Swift集合类型的核心价值在于其"值语义"和"写时复制"(Copy-on-Write)机制。从源码实现来看,这种机制通过引用计数和延迟复制来实现高效的内存管理:
extension Array {
@inlinable
internal mutating func _ensureUniqueBufferReference() {
if _slowPath(!isKnownUniquelyReferenced(&_buffer)) {
_copyToNewBuffer()
}
}
@inlinable
internal mutating func _copyToNewBuffer() {
let oldBuffer = _buffer
_buffer = _ArrayBuffer(capacity: oldBuffer.count)
_buffer.append(contentsOf: oldBuffer)
}
}
这种设计使得集合在传递时不会立即复制内存,只有在需要修改时才会创建新的副本,大大提高了性能。
1.3 集合类型的技术演进
Swift集合类型的实现经历了多次重大优化。在早期版本中,集合操作主要依赖于具体类型的实现,而从Swift 4开始,引入了协议扩展和关联类型约束,使得集合操作更加泛型化和高效。
例如,map
方法的实现从早期的具体实现转变为基于协议的泛型实现:
// 早期实现(简化版)
extension Array {
func map<T>(_ transform: (Element) -> T) -> [T] {
var result = [T]()
result.reserveCapacity(count)
for element in self {
result.append(transform(element))
}
return result
}
}
// 现代实现(基于协议扩展)
extension Sequence {
func map<T>(_ transform: (Element) -> T) -> [T] {
return _sequenceMap(transform)
}
}
这种转变不仅减少了代码冗余,还使得所有遵循Sequence
协议的类型都能自动获得map
功能。
二、Array源码分析
2.1 Array的基本结构
Array
的核心是_ArrayBuffer
结构体,它负责管理底层的内存存储。从源码来看,_ArrayBuffer
是一个泛型结构体,使用UnsafeMutablePointer
来管理内存:
@usableFromInline
internal struct _ArrayBuffer<Element> {
@usableFromInline
internal var _baseAddress: UnsafeMutablePointer<Element>?
@usableFromInline
internal var _countAndCapacity: (count: Int, capacity: Int)
// ... 其他内存管理相关属性
}
Array
通过_buffer
属性访问这个内部缓冲区,并提供了一系列安全的访问方法。例如,下标访问会进行边界检查:
extension Array {
@inlinable
public subscript(position: Int) -> Element {
_precondition(position < count, "Index out of range")
return _buffer.withUnsafeMutablePointerToElements { $0[position] }
}
}
2.2 内存管理机制
Array
的内存管理遵循"小容量栈存储,大容量堆存储"的策略。对于小型数组,Swift会使用栈上的固定缓冲区来避免堆分配:
// 简化版的小容量存储逻辑
@usableFromInline
internal struct _ContiguousArrayStorage<Element> {
// 小型数组的固定缓冲区大小(通常为16字节)
@usableFromInline
internal var _stackBuffer: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
// 堆存储指针
@usableFromInline
internal var _heapBuffer: UnsafeMutablePointer<Element>?
// 判断是否使用栈存储
@inlinable
internal var _isStackStorage: Bool {
return _heapBuffer == nil
}
}
当数组元素数量超过栈缓冲区大小时,会自动迁移到堆存储。扩容操作采用指数增长策略,通常是每次扩容为原来的2倍:
extension Array {
@inlinable
internal mutating func _growIfNeededAndReplaceElement(at index: Int, with newValue: Element) {
if _slowPath(count == _buffer.capacity) {
_reserveCapacityForAppend(1)
}
_buffer.withUnsafeMutablePointerToElements { $0[index] = newValue }
}
@inlinable
internal mutating func _reserveCapacityForAppend(_ elementsToAdd: Int) {
let newCapacity = _buffer.capacity == 0 ?
Swift.max(16, elementsToAdd) :
Swift.max(_buffer.capacity * 2, _buffer.count + elementsToAdd)
_allocateUninitializedStorage(count: _buffer.count, capacity: newCapacity)
}
}
2.3 元素访问与操作
Array
的元素访问通过下标实现,其核心是通过UnsafeMutablePointer
进行内存访问:
extension Array {
@inlinable
internal func _element(at index: Int) -> Element {
return _buffer.withUnsafeMutablePointerToElements { $0[index] }
}
}
插入和删除操作则涉及到元素的移动和内存调整。例如,在数组中间插入元素时,需要将后续元素向后移动:
extension Array {
@inlinable
public mutating func insert(_ newElement: Element, at i: Int) {
_precondition(i <= count, "Index out of range")
_makeUniqueAndReserveCapacityIfNotAlready()
// 移动后续元素
_buffer.withUnsafeMutablePointerToElements { bufferPtr in
let count = _buffer.count
if i < count {
memmove(bufferPtr + i + 1, bufferPtr + i,
MemoryLayout<Element>.size * (count - i))
}
bufferPtr[i] = newElement
_buffer._countAndCapacity.count += 1
}
}
}
三、Dictionary源码分析
3.1 Dictionary的基本结构
Dictionary
的核心是哈希表实现,采用开放寻址法(Open Addressing)解决哈希冲突。从源码来看,Dictionary
内部使用_HashTable
结构体管理键值对:
@usableFromInline
internal struct _HashTable<Key: Hashable, Value> {
@usableFromInline
internal var _storage: UnsafeMutablePointer<_HashTableStorage>
@usableFromInline
internal var _count: Int
@usableFromInline
internal var _capacity: Int
// 负载因子阈值
@usableFromInline
internal let _maxLoadFactor: Float = 0.75
}
每个存储槽(bucket)可以是三种状态之一:空(Empty)、已占用(Occupied)或已删除(Deleted):
@usableFromInline
internal enum _HashTableBucket<Key, Value> {
case empty
case occupied(hashValue: Int, key: Key, value: Value)
case deleted
}
3.2 哈希函数与冲突解决
Dictionary
使用键的hash(into:)
方法计算哈希值,并通过掩码操作将哈希值映射到存储槽索引:
extension _HashTable {
@inlinable
internal func _bucketIndex(for hashValue: Int) -> Int {
return hashValue & (_capacity - 1)
}
}
当发生哈希冲突时,采用线性探测(Linear Probing)方法寻找下一个可用槽位:
extension _HashTable {
@inlinable
internal func _findElement(byKey key: Key) -> (bucket: Int, exists: Bool) {
let hashValue = key._rawHashValue
var bucket = _bucketIndex(for: hashValue)
let firstBucket = bucket
repeat {
switch _storage[bucket] {
case .empty:
return (bucket: bucket, exists: false)
case .occupied(let storedHash, let storedKey, _) where storedHash == hashValue && storedKey == key:
return (bucket: bucket, exists: true)
default:
bucket = (bucket + 1) & (_capacity - 1)
}
} while bucket != firstBucket
return (bucket: bucket, exists: false)
}
}
3.3 键值对操作
插入操作首先检查是否需要扩容,然后找到合适的槽位插入键值对:
extension Dictionary {
@inlinable
public mutating func updateValue(_ value: Value, forKey key: Key) -> Value? {
_table._ensureUnique()
if _table._capacity * _table._maxLoadFactor <= Float(_table._count + 1) {
_table._resize(to: _table._capacity * 2)
}
let (bucket, exists) = _table._findElement(byKey: key)
let oldValue = exists ? _table._storage[bucket].value : nil
_table._storage[bucket] = .occupied(
hashValue: key._rawHashValue,
key: key,
value: value
)
if !exists {
_table._count += 1
}
return oldValue
}
}
扩容操作会创建一个新的更大的哈希表,并重新哈希所有元素:
extension _HashTable {
@inlinable
internal mutating func _resize(to newCapacity: Int) {
let oldStorage = _storage
let oldCount = _count
let oldCapacity = _capacity
_allocateStorage(capacity: newCapacity)
_count = 0
for i in 0..<oldCapacity {
switch oldStorage[i] {
case .occupied(let hashValue, let key, let value):
let (bucket, exists) = _findElement(byKey: key)
precondition(!exists)
_storage[bucket] = .occupied(hashValue: hashValue, key: key, value: value)
_count += 1
default:
continue
}
}
// 释放旧内存
_deallocateStorage(oldStorage, capacity: oldCapacity)
}
}
四、Set源码分析
4.1 Set的基本结构
Set
在内部实际上是通过Dictionary
实现的,它将元素作为键存储,而值则使用一个占位类型Empty
:
public struct Set<Element: Hashable> {
@usableFromInline
internal var _storage: Dictionary<Element, Void>
// ... 其他属性和方法
}
这种设计使得Set
能够复用Dictionary
的哈希表实现,同时保证元素的唯一性。
4.2 元素唯一性保证
Set
的元素唯一性是通过Dictionary
的键唯一性实现的。当插入一个元素时,实际上是在Dictionary
中插入一个键值对,其中值为占位符:
extension Set {
@inlinable
public mutating func insert(_ newMember: Element) -> (inserted: Bool, memberAfterInsert: Element) {
let result = _storage.updateValue((), forKey: newMember)
return (inserted: result == nil, memberAfterInsert: newMember)
}
}
由于Dictionary
的键必须唯一,因此Set
中的元素也必然唯一。
4.3 集合操作实现
Set
的集合操作(如并集、交集、差集)都是基于哈希表的高效实现。例如,并集操作通过遍历两个集合的元素并插入到新集合中实现:
extension Set {
@inlinable
public func union(_ other: Set) -> Set {
var result = self
for element in other {
result.insert(element)
}
return result
}
}
交集操作则遍历一个集合的元素,检查另一个集合中是否存在相同元素:
extension Set {
@inlinable
public func intersection(_ other: Set) -> Set {
var result = Set()
for element in self {
if other.contains(element) {
result.insert(element)
}
}
return result
}
}
五、集合协议层次结构
5.1 协议定义与关系
Swift的集合协议形成了一个严格的层次结构,从最基础的Sequence
协议开始,逐步扩展出更高级的功能:
// 协议层次结构简化示意图
Sequence
↓
Collection
↓
BidirectionalCollection
↓
RandomAccessCollection
每个协议都添加了特定的约束和功能。例如,BidirectionalCollection
协议添加了反向遍历的能力:
public protocol BidirectionalCollection: Collection {
func index(before i: Index) -> Index
}
而RandomAccessCollection
协议则进一步添加了高效随机访问的约束:
public protocol RandomAccessCollection: BidirectionalCollection {
associatedtype Indices: RandomAccessCollection = DefaultIndices<Self>
associatedtype SubSequence: RandomAccessCollection = Self.SubSequence
func distance(from start: Index, to end: Index) -> Int
func index(_ i: Index, offsetBy n: Int) -> Index
}
5.2 集合类型的协议遵循
Array
、Dictionary
和Set
分别遵循了不同层次的集合协议:
Array
遵循RandomAccessCollection
,支持高效的随机访问和双向遍历。Dictionary
遵循Collection
,但不支持双向遍历,因为键值对的存储是无序的。Set
遵循Collection
和SetAlgebra
,支持集合运算和元素唯一性。
这些协议遵循关系在源码中通过扩展实现。例如,Array
对RandomAccessCollection
的遵循:
extension Array: RandomAccessCollection {
// 实现RandomAccessCollection要求的方法
public func distance(from start: Int, to end: Int) -> Int {
return end - start
}
public func index(_ i: Int, offsetBy n: Int) -> Int {
return i + n
}
}
5.3 协议扩展的应用
协议扩展是Swift集合实现的核心技术之一,它允许为协议提供默认实现,从而减少代码冗余。例如,Sequence
协议的map
方法就是通过协议扩展实现的:
extension Sequence {
@inlinable
public func map<T>(_ transform: (Element) -> T) -> [T] {
let initialCapacity = underestimatedCount
var result = ContiguousArray<T>()
result.reserveCapacity(initialCapacity)
var iterator = makeIterator()
// 预分配空间并填充
for _ in 0..<initialCapacity {
result.append(transform(iterator.next()!))
}
// 处理剩余元素
while let element = iterator.next() {
result.append(transform(element))
}
return Array(result)
}
}
这种实现方式使得任何遵循Sequence
协议的类型都能自动获得map
功能,无需重复实现。
六、泛型与集合类型
6.1 泛型的基本概念
泛型是Swift的核心特性之一,它允许编写与类型无关的代码,提高代码的复用性。在集合类型中,泛型被广泛应用,使得集合可以存储任意类型的元素。
6.2 集合类型的泛型实现
所有Swift集合类型都是泛型的。例如,Array
的定义使用了泛型参数Element
:
public struct Array<Element> {
// ... 实现细节
}
Dictionary
和Set
也类似:
public struct Dictionary<Key: Hashable, Value> {
// ... 实现细节
}
public struct Set<Element: Hashable> {
// ... 实现细节
}
这些泛型参数在集合的实现中被用于指定元素的类型,使得集合可以存储任意符合约束的类型。
6.3 泛型约束的应用
泛型约束是泛型的重要组成部分,它允许对泛型参数进行限制。在集合类型中,泛型约束被广泛用于确保集合操作的安全性和正确性。
例如,Dictionary
要求其键类型必须遵循Hashable
协议:
public struct Dictionary<Key: Hashable, Value> {
// ... 实现细节
}
这是因为Dictionary
的哈希表实现需要使用键的哈希值来进行元素存储和查找。同样,Set
也要求其元素类型遵循Hashable
协议。
在协议扩展中,泛型约束也被用于提供更具体的默认实现。例如,为所有遵循Equatable
协议的元素类型提供更高效的contains
方法:
extension Sequence where Element: Equatable {
@inlinable
public func contains(_ element: Element) -> Bool {
for e in self {
if e == element {
return true
}
}
return false
}
}
七、集合类型的性能优化
7.1 Array的性能优化
Array
的性能优化主要集中在内存管理和元素操作上。如前所述,Array
采用写时复制机制避免不必要的复制,同时使用指数级扩容策略减少扩容次数。
此外,Array
还针对特定操作进行了优化。例如,append
方法在大多数情况下具有O(1)的平摊时间复杂度:
extension Array {
@inlinable
public mutating func append(_ newElement: Element) {
_makeUniqueAndReserveCapacityIfNotAlready()
_buffer.appendElement(newElement)
}
}
7.2 Dictionary的性能优化
Dictionary
的性能优化主要围绕哈希表展开。为了减少哈希冲突,Dictionary
使用高质量的哈希函数,并在负载因子过高时进行扩容。
扩容操作的时间复杂度是O(n),但由于采用了指数级扩容策略,平摊时间复杂度仍然是O(1)。此外,Dictionary
还针对小容量情况进行了优化,使用更紧凑的内存布局。
7.3 Set的性能优化
Set
继承了Dictionary
的性能优化,因为它内部使用Dictionary
实现。Set
的插入、删除和查找操作的平均时间复杂度都是O(1)。
对于集合操作(如并集、交集),Set
会根据集合的大小选择最优的算法。例如,当一个集合远小于另一个集合时,交集操作会遍历较小的集合,检查每个元素是否存在于较大的集合中,从而减少总体操作次数。
八、集合类型的线程安全
8.1 线程安全的基本概念
线程安全是指在多线程环境下,对共享资源的访问不会导致数据不一致或其他并发问题。在Swift中,集合类型默认不是线程安全的,因为线程安全会带来额外的性能开销。
8.2 Swift集合类型的线程安全特性
Swift的集合类型(如Array
、Dictionary
和Set
)在设计上没有内置的线程同步机制。这意味着如果多个线程同时访问和修改同一个集合实例,可能会导致数据竞争和其他并发问题。
例如,以下代码在多线程环境下是不安全的:
var array = [Int]()
// 线程1
DispatchQueue.global().async {
for i in 0..<1000 {
array.append(i)
}
}
// 线程2
DispatchQueue.global().async {
for i in 1000..<2000 {
array.append(i)
}
}
8.3 实现线程安全的集合类型
要实现线程安全的集合类型,可以通过封装原始集合类型并添加同步机制。例如,使用NSLock
实现一个线程安全的数组:
class ThreadSafeArray<T> {
private var array = [T]()
private let lock = NSLock()
func append(_ element: T) {
lock.lock()
defer { lock.unlock() }
array.append(element)
}
func remove(at index: Int) -> T? {
lock.lock()
defer { lock.unlock() }
guard index < array.count else { return nil }
return array.remove(at: index)
}
func count() -> Int {
lock.lock()
defer { lock.unlock() }
return array.count
}
}
另一种更高效的方法是使用ConcurrentValue
模式,它通过复制和合并操作来减少锁的持有时间:
class ConcurrentValue<T> {
private var _value: T
private let queue = DispatchQueue(label: "ConcurrentValueQueue", attributes: .concurrent)
init(_ value: T) {
self._value = value
}
var value: T {
return queue.sync { _value }
}
func modify(_ closure: (inout T) -> Void) {
queue.sync(flags: .barrier) {
closure(&self._value)
}
}
}
// 使用示例
let concurrentArray = ConcurrentValue([Int]())
concurrentArray.modify { $0.append(42) }
九、集合类型的扩展与自定义
9.1 集合类型的标准扩展
Swift的集合类型通过协议扩展提供了丰富的功能。例如,Sequence
协议的扩展提供了map
、filter
、reduce
等常用方法:
extension Sequence {
@inlinable
public func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) -> Result) -> Result {
var accumulator = initialResult
for element in self {
accumulator = nextPartialResult(accumulator, element)
}
return accumulator
}
}
Collection
协议的扩展则提供了更多与集合结构相关的方法,如firstIndex(where:)
:
extension Collection {
@inlinable
public func firstIndex(where predicate: (Element) -> Bool) -> Index? {
var idx = startIndex
while idx != endIndex {
if predicate(self[idx]) {
return idx
}
formIndex(after: &idx)
}
return nil
}
}
9.2 自定义集合类型
要自定义集合类型,需要遵循适当的协议。例如,创建一个简单的自定义集合:
struct CustomCollection<Element>: Collection {
private var elements: [Element]
init(_ elements: [Element]) {
self.elements = elements
}
// 实现Collection协议要求的属性和方法
typealias Index = Int
var startIndex: Int { return 0 }
var endIndex: Int { return elements.count }
subscript(position: Int) -> Element {
return elements[position]
}
func index(after i: Int) -> Int {
return i + 1
}
}
这个自定义集合现在可以使用所有Collection
协议提供的方法,如map
、filter
等。
9.3 扩展现有集合类型
扩展现有集合类型是Swift中常用的技术。例如,为Array
添加一个安全的下标:
extension Array {
subscript(safe index: Int) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
或者为所有遵循Collection
协议的类型添加一个second
属性:
extension Collection where Indices.Iterator.Element == Index {
var second: Element? {
return indices.contains(index(after: startIndex)) ? self[index(after: startIndex)] : nil
}
}
十、集合类型的应用场景
10.1 Array的应用场景
Array
适用于各种需要有序存储元素的场景。例如:
- 列表数据展示:在UI开发中,
Array
常用于存储和展示列表数据。 - 栈和队列实现:
Array
可以轻松实现栈和队列等数据结构。 - 数据排序和搜索:由于
Array
支持随机访问,它非常适合排序和搜索算法。
10.2 Dictionary的应用场景
Dictionary
适用于需要通过键快速查找值的场景。例如:
- 缓存系统:使用
Dictionary
存储键值对,实现高效的缓存。 - 配置管理:存储配置信息,通过键快速获取配置值。
- 数据映射:在JSON解析等场景中,使用
Dictionary
映射数据结构。
10.3 Set的应用场景
Set
适用于需要保证元素唯一性的场景。例如:
- 去重操作:使用
Set
可以轻松去除数组中的重复元素。 - 集合运算:实现交集、并集、差集等集合运算。
- 成员检查:快速检查元素是否存在于集合中。
十一、集合类型的最佳实践
11.1 选择合适的集合类型
在实际开发中,选择合适的集合类型至关重要。根据数据的特性和使用场景,选择最适合的集合类型可以提高代码的性能和可读性。
例如,如果需要有序存储且允许重复元素,选择Array
;如果需要通过键快速查找值,选择Dictionary
;如果需要保证元素唯一性,选择Set
。
11.2 优化集合操作
为了提高性能,应尽量优化集合操作。例如:
- 预先分配足够的容量,避免频繁扩容。
- 使用更高效的算法,如使用
Set
进行成员检查,而不是Array
。 - 避免在循环中频繁修改集合结构,因为这可能导致元素移动和内存重新分配。
11.3 处理集合类型的空值
在使用集合类型时,需要注意处理空值的情况。例如,在访问数组元素时,确保索引在有效范围内:
let array = [1, 2, 3]
if let element = array[safe: 5] {
// 处理元素
} else {
// 处理索引越界
}
在处理可能为空的集合时,可以使用可选链和空合运算符:
let optionalArray: [Int]? = nil
let count = optionalArray?.count ?? 0
十二、集合类型与其他Swift特性的交互
12.1 集合类型与闭包
集合类型与闭包的结合使用是Swift的强大特性之一。通过闭包,可以方便地对集合元素进行转换、过滤和聚合操作。
例如,使用map
和filter
处理数组:
let numbers = [1, 2, 3, 4, 5]
let squaredEvenNumbers = numbers
.filter { $0 % 2 == 0 }
.map { $0 * $0 }
12.2 集合类型与协议
集合类型与协议的结合使得代码更加灵活和可复用。通过遵循不同的协议,集合类型可以获得不同的功能。
例如,通过遵循Codable
协议,集合类型可以支持JSON编码和解码:
struct Person: Codable {
let name: String
let age: Int
}
let people = [Person(name: "Alice", age: 30), Person(name: "Bob", age: 25)]
let encoder = JSONEncoder()
if let jsonData = try? encoder.encode(people) {
// 处理JSON数据
}
12.3 集合类型与泛型
集合类型与泛型的结合是Swift集合设计的核心。泛型使得集合可以存储任意类型的元素,提高了代码的灵活性和可复用性。
例如,Array
、Dictionary
和Set
都是泛型类型,可以根据需要存储不同类型的元素:
let names: [String] = ["Alice", "Bob", "Charlie"]
let ages: [String: Int] = ["Alice": 30, "Bob": 25, "Charlie": 35]
let uniqueNumbers: Set<Int> = [1, 2, 3, 3, 4] // 自动去重
十三、集合类型的未来发展趋势
13.1 Swift语言发展对集合类型的影响
随着Swift语言的不断发展,集合类型也会不断演进。例如,Swift可能会引入更高级的泛型特性,进一步增强集合类型的表达能力。
此外,Swift对性能的持续优化也会反映在集合类型的实现上,未来的集合类型可能会采用更高效的内存管理和算法。
13.2 并发编程对集合类型的需求
随着并发编程的普及,对线程安全集合类型的需求也会增加。未来的Swift可能会提供更高效、更易用的线程安全集合类型,或者改进现有的集合类型,使其更容易在并发环境中使用。
13.3 与其他技术的集成
集合类型可能会与其他技术进行更深入的集成。例如,与机器学习框架集成,提供更高效的数据处理和分析功能;与网络编程集成,提供更便捷的数据传输和处理方式。