Elixir中的所有内容都是引用类型吗?

阅读 Elixir 不变性以及它如何尽可能避免内存复制,这似乎是唯一可能的解释,但我还没有在任何地方看到它明确说明。例如,当将一个新元素添加到一个列表时,它被描述为该操作恰好需要 n 步,其中 n 是列表的长度,但它只是对原始元素进行浅拷贝。所以我的假设是:

假设我们有一个列表 [1, 2, 3, 4]。它由 4 个节点组成,但节点本身不包含值。1、2、3、4 存储在其他地方,每个节点都包含对相应值的引用,以及对下一个节点的引用。当我们将 10 添加到列表中时,不仅创建了一个而是实际上创建了五个新节点,因为在原始列表中,数字 4 的节点必须将“nil”作为其“下一个”引用,但在新列表中,节点数字 4 必须有“next”指向新创建的数字 10 节点。所以它不能重复使用。这反过来意味着数字 3 的节点也不能重用,等等。所以创建了五个新节点,但前四个是浅拷贝,这意味着它们指向与原始节点完全相同的内存位置节点。

我刚才描述的有意义吗?

回答

它如何尽可能避免内存复制

我认为您指的是结构共享,这是持久数据结构的属性
不仅是不可变的,而且还能够通过生成重用底层结构的一部分的修改副本来有效地对它们进行操作。

链表就是一个例子,但只支持有效的前置。正如您所指出的,追加需要从头开始完全重建列表。不可能重用(结构共享)。

l1 = [1, 2, 3]实际上是[1 | [2 | [3 | []]]].

l2 = l1 ++ [4] ( [1, 2, 3, 4]) 实际上[1 | [2 | [3 | [4 | []]]]]并且不包含/重用l1.

另一方面,l3 = [0 | l1]或者[0] ++ l1[0 | [1 | [2 | [3 | []]]]]重用l1.

一种可视化它如何指向引擎盖下的相同结构的方法:

iex> Enum.take(l2, 3)
[1, 2, 3]
iex> Enum.drop(l3, 1)
[1, 2, 3]
iex> :erts_debug.same Enum.take(l2, 3), l1  # no structural sharing
false
iex> :erts_debug.same Enum.drop(l3, 1), l1  # structural sharing
true

通过连续的前置(在递归/reduce 中)并最终反转结果来构建列表是很常见的。

# artifical implementation of Enum.map
Enum.reduce(enum, [], fn x, acc -> [f.(x) | acc] end) |> Enum.reverse()

Erlang 效率指南中的这一部分提供了很好的解释。

其他数据结构,例如Maps, :arrays 或:gb_trees也利用结构共享:例如,在执行 时Map.put(my_big_map, 0, 0),引擎盖下的树结构(从技术上讲是哈希数组映射的 trie)可以大部分重用并且无需对 进行深度复制my_big_map

iex> my_big_map = Map.new 1..10000, fn x -> {x, x} end
iex> new_map = Map.put(my_big_map, 0, 0)
iex> :erts_debug.size my_big_map
37898
iex> :erts_debug.size [my_big_map, new_map]  # almost no copy needed
37958


以上是Elixir中的所有内容都是引用类型吗?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>