了解 swift 数组如何在底层工作,以及如何优化它们——使用 ContiguousArray、它们的比较等。

swift 解析数据(七爪源码深入了解)(1)

增加数组的大小

每个数组保留特定数量的内存来存储其内容。 当您将项目添加到数组并且该数组开始超过其保留容量时,该数组会分配大量内存并将其项目复制到新的保管库,其中新保管库的大小与旧保管库的许多大小相同。

swift 解析数据(七爪源码深入了解)(2)

这种指数增长策略意味着追加一个元素在恒定时间内发生,平均许多追加操作的性能。 触发重新分配的追加操作有性能成本,但随着阵列变大,它们发生的频率越来越低。

示例:创建一个包含 10 个元素的数组。

Swift 会为这个数组分配足够的容量来存储这 10 个元素,所以 array.capacity 和 array.count 都等于 10。

var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] print(array.capacity) // 10 print(array.count) // 10

让我们添加 11 和 12 个元素。 数组没有这个容量,所以它需要释放空间——它会找到内存来存储更多元素,将数组复制到那里,然后添加 11 和 12 个元素。 这些插入的执行时间很复杂——O(n),其中 n 是数组中元素的数量。

array.append(11) array.append(12) print(array.capacity) // 20 print(array.count) // 12

因此,当您将 11 和 12 个元素添加到容量为 10 的数组时,swift 会创建一个大小为 20 的数组。当我们超过这个大小时,下一个容量将是 40,然后是 80,汗水 160,等等 上。

如果您知道大约要保存多少项目,请使用 reserveCapacity(_:)。 这将允许您避免中间重新分配。

使用容量和计数属性来确定一个数组可以存储多少元素而不分配大量资源。

ar stringArray = Array<String>() stringArray.reserveCapacity(128)

如果您的数组的 Element 类型是类或 @objc 协议,并且您不需要将数组桥接到 NSArray 或将数组传递给 Objective-C API,则使用 ContiguousArray 可能比 Array 更高效且性能更可预测。 如果数组的 Element 类型是结构体或枚举,Array 和 ContiguousArray 的效率应该差不多。

swift 解析数据(七爪源码深入了解)(3)

ContiguousArray 类型是一个专门的数组,它始终将其元素存储在一个连续的内存区域中。 这与 Array 形成对比,后者可以将其元素存储在内存的连续区域或 NSArrayinstance 中,如果其元素类型是类或 @objc 协议。

数组将连续存储:

Array 和 ContiguousArray 的比较

import Foundation protocol Possibles { init(repeating: Bool, count: Int) subscript(index: Int) -> Bool { get set } var count: Int { get } } extension Array: Possibles where Element == Bool {} extension ContiguousArray: Possibles where Element == Bool {} func lazySieveOfEratosthenes<Storage: Possibles>(makeStorage: () -> Storage) -> [Int] { var possibles = makeStorage() let limit = possibles.count - 1 return (2 ... limit).lazy.filter { i in // Lazy, so that `possibles` is updated before it is used. possibles[i] }.map { i in stride(from: i * i, through: limit, by: i).lazy.forEach { j in possibles[j] = false } return i }.reduce(into: [Int]()) { (result, next) in result.append(next) } } func forSieveOfEratosthenes<Storage: Possibles>(makeStorage: () -> Storage) -> [Int] { var possibles = makeStorage() let limit = possibles.count - 1 var result = [Int]() for i in 2 ... limit where possibles[i] { var j = i * i while j <= limit { possibles[j] = false j = i } result.append(i) } return result } func test(_ name: String, _ storageType: String, _ function: (Int) -> [Int]) { let start = Date() let result = function(100_000) let end = Date() print("\(name) \(storageType) biggest: \(result.last ?? 0) time: \(end.timeIntervalSince(start))") } test("for ", "Array ") { limit in forSieveOfEratosthenes { Array(repeating: true, count: limit 1) } } test("for ", "ContiguousArray") { limit in forSieveOfEratosthenes { ContiguousArray(repeating: true, count: limit 1) } } test("lazy ", "Array ") { limit in lazySieveOfEratosthenes { Array(repeating: true, count: limit 1) } } test("lazy ", "ContiguousArray") { limit in lazySieveOfEratosthenes { ContiguousArray(repeating: true, count: limit 1) } }

结果:

for Array biggest: 99991 time: 41.016937017440796 for ContiguousArray biggest: 99991 time: 40.648478984832764 lazy Array biggest: 99991 time: 3.3549970388412476 lazy ContiguousArray biggest: 99991 time: 3.5851539373397827

另一个:

for Array biggest: 99991 time: 41.801795959472656 for ContiguousArray biggest: 99991 time: 42.37710893154144 lazy Array biggest: 99991 time: 3.438219904899597 lazy ContiguousArray biggest: 99991 time: 3.4085270166397095

启动是在具有以下配置的笔记本电脑上完成的:

代码示例取自here,我建议您查看整个线程。 如您所见,Array 并不总是比 ContiguousArray 慢。

带类的数组

如果 Array 中的元素是类的实例,则语义是相同的,尽管它们起初看起来可能不同。 在这种情况下,存储在数组中的值是对数组外对象的引用。 当您更改单个数组中的对象引用时,只有该数组具有对新对象的引用。 然而,如果两个数组包含对同一个对象的引用,您可以从两个数组观察到该对象属性的变化。

class MyClass { var name = "Name" } var firstArray = [MyClass(), MyClass()] var secondArray = firstArray firstArray[0].name = "Another Name" print(firstArray[0].name) // "Another name" print(secondArray[0].name) // "Another name

firstArray[0] = MyClass() print(firstArray[0].name) // "Name" print(secondArray[0].name) // "Another name

Big-O 算法复杂性与操作:

let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] array[3]

var people = ["Sandra", "Mike", "James", "Donald"] people.sort()

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] var sumOfNumbers = 0 numbers.forEach { sumOfNumbers = $0 } print(sumOfNumbers) // 55

var twoDimensionalArray = [["first one", "second one"], ["first two", "second two"], ["first third", "second third"]] for i in 0..<twoDimensionalArray.count { for n in 0..<twoDimensionalArray[i].count { print(twoDimensionalArray[i][n]) } }

结论

Swift 中的数组是一种很好的数据类型,用于组织有序集合,您可以在其中存储各种元素,但请记住值和引用类型的语义。 您可以执行各种操作(添加、删除等),不仅可以使用 Array,还可以使用 ContiguousArray、NSArray、ArraySliceand 等。

谢谢阅读。

,