Swift 如何在内部管理数组?
How does Swift manage Arrays internally?
我想知道 Swift 如何在内部管理数组? Apple's language guide只讲用法,不详述内部结构
作为一名 Java 开发人员,我习惯于将 "bare" 数组视为一种非常静态和固定的数据结构。我知道这在 Swift 中不是真的。在 Swift 中,与 Java 不同,您可以改变数组的长度,还可以执行插入和删除操作。在 Java 我习惯于根据我想对该结构执行的操作来决定我想使用什么数据结构(简单数组、ArrayList、LinkedList 等),从而优化我的代码以获得更好的性能。
总之,我想知道数组在Swift中是如何实现的。它们在内部被管理为(双)链表吗?有没有什么可以与 Java 的收集框架相媲美的,以便调整以获得更好的性能?
您可以在 Swift 标准库中的上方评论中找到有关 Array
的大量信息。要查看此内容,您可以在 playground 中按住 cmd-opt-click Array
,或者您可以在非官方 SwiftDoc 页面中查看它。
从那里解释一些信息来回答你的问题:
在 Swift 中创建的数组将它们的值保存在连续的内存区域中。因此,您可以有效地将 Swift 数组传递给需要这种结构的 C API。
正如您提到的,数组可以随着您向其附加值而增长,在某些时候,这意味着分配了一个新的、更大的内存区域,并将以前的值复制到其中。正是出于这个原因,它声明像 append may 这样的操作是 O(n)
– 也就是说,执行追加操作的最坏情况时间与当前大小成比例增长数组的(因为复制值所花费的时间)。
然而,当阵列必须增加其存储时,它每次分配的新存储量呈指数增长,这意味着随着您追加,重新分配变得越来越少,这意味着 "amortized" 时间追加所有调用接近恒定时间。
数组还有一个方法,reserveCapacity
,它允许您通过请求数组预先为自己分配一些最小数量的 space 来抢先避免调用 append 时的重新分配。如果您提前知道您计划在数组中保留多少个值,则可以使用它。
将一个新值插入数组的中间也是O(n)
,因为数组保存在连续的内存中,所以插入一个新值涉及将后续值洗牌到末尾。与追加不同的是,这并没有比多次调用有所改善。这与可以在 O(1)
中插入的 linked 列表非常不同,即常数时间。但请记住,与 linked 列表不同,数组也可以在恒定时间内随机访问。
就地更改数组中的单个值(即通过下标分配)应该是 O(1)
(subscript
实际上没有文档注释,但这是一个非常安全的选择).这意味着如果您创建一个数组,填充它,然后不向其中追加或插入,就性能而言,它的行为应该类似于 Java 数组。
所有这一切都有一个警告 – 数组具有 "value" 语义。这意味着如果您有一个数组变量 a
,并将其分配给另一个数组变量 b
,这实际上是在复制数组。对 a
中值的后续更改不会影响 b
,更改 b
也不会影响 a
。这与 "reference" 语义不同,其中 a
和 b
都指向同一个数组,并且通过 a
对其进行的任何更改都会反映给通过 [= 查看它的人19=].
然而,Swift数组实际上是"Copy-on-Write"。也就是说,当您将 a
分配给 b
时,实际上不会发生任何复制。它仅在两个变量之一发生更改时发生 ("mutated")。这带来了很大的性能优势,但这确实意味着如果两个阵列引用相同的存储,因为自复制以来都没有执行过写入,那么像下标分配这样的更改确实会一次性复制整个阵列点.
在大多数情况下,除非在极少数情况下(尤其是在处理中小型数组时),否则您无需担心这些,但如果性能对您至关重要,那绝对值得熟悉 link.
中的所有文档
我想知道 Swift 如何在内部管理数组? Apple's language guide只讲用法,不详述内部结构
作为一名 Java 开发人员,我习惯于将 "bare" 数组视为一种非常静态和固定的数据结构。我知道这在 Swift 中不是真的。在 Swift 中,与 Java 不同,您可以改变数组的长度,还可以执行插入和删除操作。在 Java 我习惯于根据我想对该结构执行的操作来决定我想使用什么数据结构(简单数组、ArrayList、LinkedList 等),从而优化我的代码以获得更好的性能。
总之,我想知道数组在Swift中是如何实现的。它们在内部被管理为(双)链表吗?有没有什么可以与 Java 的收集框架相媲美的,以便调整以获得更好的性能?
您可以在 Swift 标准库中的上方评论中找到有关 Array
的大量信息。要查看此内容,您可以在 playground 中按住 cmd-opt-click Array
,或者您可以在非官方 SwiftDoc 页面中查看它。
从那里解释一些信息来回答你的问题:
在 Swift 中创建的数组将它们的值保存在连续的内存区域中。因此,您可以有效地将 Swift 数组传递给需要这种结构的 C API。
正如您提到的,数组可以随着您向其附加值而增长,在某些时候,这意味着分配了一个新的、更大的内存区域,并将以前的值复制到其中。正是出于这个原因,它声明像 append may 这样的操作是 O(n)
– 也就是说,执行追加操作的最坏情况时间与当前大小成比例增长数组的(因为复制值所花费的时间)。
然而,当阵列必须增加其存储时,它每次分配的新存储量呈指数增长,这意味着随着您追加,重新分配变得越来越少,这意味着 "amortized" 时间追加所有调用接近恒定时间。
数组还有一个方法,reserveCapacity
,它允许您通过请求数组预先为自己分配一些最小数量的 space 来抢先避免调用 append 时的重新分配。如果您提前知道您计划在数组中保留多少个值,则可以使用它。
将一个新值插入数组的中间也是O(n)
,因为数组保存在连续的内存中,所以插入一个新值涉及将后续值洗牌到末尾。与追加不同的是,这并没有比多次调用有所改善。这与可以在 O(1)
中插入的 linked 列表非常不同,即常数时间。但请记住,与 linked 列表不同,数组也可以在恒定时间内随机访问。
就地更改数组中的单个值(即通过下标分配)应该是 O(1)
(subscript
实际上没有文档注释,但这是一个非常安全的选择).这意味着如果您创建一个数组,填充它,然后不向其中追加或插入,就性能而言,它的行为应该类似于 Java 数组。
所有这一切都有一个警告 – 数组具有 "value" 语义。这意味着如果您有一个数组变量 a
,并将其分配给另一个数组变量 b
,这实际上是在复制数组。对 a
中值的后续更改不会影响 b
,更改 b
也不会影响 a
。这与 "reference" 语义不同,其中 a
和 b
都指向同一个数组,并且通过 a
对其进行的任何更改都会反映给通过 [= 查看它的人19=].
然而,Swift数组实际上是"Copy-on-Write"。也就是说,当您将 a
分配给 b
时,实际上不会发生任何复制。它仅在两个变量之一发生更改时发生 ("mutated")。这带来了很大的性能优势,但这确实意味着如果两个阵列引用相同的存储,因为自复制以来都没有执行过写入,那么像下标分配这样的更改确实会一次性复制整个阵列点.
在大多数情况下,除非在极少数情况下(尤其是在处理中小型数组时),否则您无需担心这些,但如果性能对您至关重要,那绝对值得熟悉 link.
中的所有文档