JavaScript 是不是对 Map 和 Set 使用哈希表?
Posted
技术标签:
【中文标题】JavaScript 是不是对 Map 和 Set 使用哈希表?【英文标题】:Does JavaScript use hashtables for Map and Set?JavaScript 是否对 Map 和 Set 使用哈希表? 【发布时间】:2021-01-16 23:07:35 【问题描述】:我是一名 Python 开发人员,在 javascript 中迈出了第一步。
我开始使用Map 和Set。它们似乎与 Python 中的 dict
和 set
具有相同的 API,所以我假设它们是一个哈希表,我可以依靠 O(1) 查找时间。
但是,出于好奇,我试图看看如果我在 Chrome 的控制台中执行此操作会发生什么:
new Set([new Set([1, 2, 3])])
会发生什么:
Set(1) Set(3)
JavaScript 愉快地创建了集合。怎么会这样?在 Python 中你会得到一个错误,因为你不能把一个可变项放在一个集合或一个字典中。为什么 JavaScript 允许?
【问题讨论】:
因为 JS 允许你散列可变类型。这不是不可能,只是不够可取,Python 不允许你这样做。当然,在 Python 中,您可以定义自己的可变和可散列的自定义类型。但是,请注意,对象是按身份进行散列的,所以它不是非常危险,但它不是很有用。 JS 允许它,因为它允许它。请注意,Java 和 C# 还允许将可变项放入哈希桶中。当然,如果它们的哈希标识不稳定,这可能会导致问题,这可能是 Python 试图避免的,但是,JS 在这里肯定没有做任何不寻常的事情。事实上,在某些方面,JS 通过不允许对象的哈希标识改变来缓解一些问题——它始终是对象的引用,这避免了在对象被分类到一个哈希桶之后“丢失”对象的问题,然后更改,因此代码在另一个中查找它。 然后,这又会产生其他问题,因为您无法在没有实际拥有对象的情况下在哈希集中查找对象。自定义散列标识将允许您通过构造散列到相同值的东西来找到它。所以,这种方法有积极和消极的一面,但同样,它并没有任何异常。 【参考方案1】:这些数据结构的内部表示取决于运行代码的引擎(例如 V8 或 Chakra)。但是,规范要求引擎在
中实现这些结构[...] 提供与集合中元素数量成次线性的访问时间的机制。
来自ECMAScript® 2021 Language Specification - 23.1 Map Objects
【讨论】:
【参考方案2】:考虑下面的 JS 代码:
> m1 = new Map([['a', 1]])
Map 'a' => 1
> m2 = new Map()
Map
> m2.set(m1, 3)
Map Map 'a' => 1 => 3
> m2.get(m1)
3
但请注意,它是基于身份的哈希,即===
,所以...
> m2.get(new Map([['a',1]]))
undefined
说真的,这张地图有多大用处?
注意,这与 Python 的默认行为没有什么不同。自定义类型的默认状态是hashable:
>>> class Foo: pass
...
>>> f0 = Foo()
>>> s = f0
>>> Foo() in s
False
>>> f0 in s
True
在 Python 中,默认情况下,object.__eq__
会根据身份进行比较,所以上面的没问题。但是,如果您覆盖 __eq__
,默认情况下,__hash__
设置为 None
,并且尝试使用基于散列的容器将失败:
>>> class Bar:
... def __init__(self, value):
... self.value = value
... def __eq__(self, other):
... return self.value == other.value
...
>>> b0 = Bar(0)
>>> b1 = Bar(2)
>>> b0, b1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'Bar'
此时,您必须实现 __hash__
以与 __eq__
保持一致,但请注意,您的用户定义对象永远不会真正非常“不可变”
【讨论】:
“说真的,这张地图有多大用处?”它可以作为一种解耦且非常便宜的方式来向对象添加元信息,而无需修改对象本身。事实上,一个键也是一个映射的映射就是一个完美的例子——你不能甚至改变 Map 类。您可以对其他第三方对象使用相同的方法来丰富它们而无需接触它们。将对象作为键添加到地图中,将您想要的任何数据添加为值(甚至可以是函数/方法),然后使用您的对象从地图中获取元信息。 @VLAZ 您可能希望为此使用 WeakKeyMap,否则您的属性表可能会无限增长。对于一个更像 ECS 的系统,您将键入 ID 而不是任意对象。 @Masklinn 是的,这取决于你到底想用这个做什么。我是在指出它如何有用。我经常看到人们声称将对象放入地图/集合中是没有用的。由于通过提出问题的答案略微触及了这一点,我想提供一个例子。我想我不需要指定在使用这种方法之前你需要考虑你的场景。 @VLAZ 另一个基于身份的成员资格有用的例子是使用Set
来跟踪某些图形结构中的访问节点。
@juanpa.arrivillaga 我现在正在试验Map
,我发现如果你添加一个Map
作为密钥,它会接受它,但是如果你尝试使用任何其他映射作为第一个映射的键,它为您提供第二个映射的值。 就好像它唯一关心的是对象的类型而不是它的内容。这是怎么回事?以上是关于JavaScript 是不是对 Map 和 Set 使用哈希表?的主要内容,如果未能解决你的问题,请参考以下文章