Java HashMap 如何为“get”操作执行恒定时间查找 O(1)?
Posted
技术标签:
【中文标题】Java HashMap 如何为“get”操作执行恒定时间查找 O(1)?【英文标题】:How is it possible for Java HashMap to perform constant time lookup O(1) for "get" operations? 【发布时间】:2015-05-08 08:47:59 【问题描述】:我了解 HashMap 如何工作的基础知识 - hm.put(obj) 根据 obj.hashCode 值找到正确的存储桶来放置对象。然后在该桶中,如果另一个对象 .equals(obj) 则替换它,如果没有将其添加到桶中。
但我不清楚 HashMap.put 和 HashMap.get 如何可以是常数时间 O(1)。据我了解,存储桶的数量应基于哈希码,因此将 100 个对象放入哈希图中将(大致)创建 100 个存储桶(我确实了解哈希码有时会发生冲突,因此它可能小于 100 但不是经常)。
所以随着添加到哈希图中的对象数量的增加,桶的数量也会增加 - 由于冲突很少,这并不意味着桶的数量几乎随着添加的对象数量线性增长,其中case HashMap.put/HashMap.get 将是 O(n),因为它必须在找到正确的存储桶之前搜索每个存储桶。
我错过了什么?
【问题讨论】:
Is a Java hashmap really O(1)? 的可能重复项 哈希表的全部意义在于它不需要搜索每个桶。 将哈希映射视为由对象的哈希码索引的存储桶数组。那是O(1)
,除非发生碰撞;桶得到更多的项目。
为什么你认为 get() 必须搜索每个桶?正如您在第一行中提到的,键的哈希码将给出数组中的存储桶位置,因此您不必搜索所有存储桶。
【参考方案1】:
哈希表不需要搜索每个bucket,就像你不需要搜索图书馆的每个书架一样,因为你可以在index cards中查找它的位置,你不需要搜索索引中的每张卡片,因为它们已排序...(不确定这是否有帮助,因为我不确定人们是否仍然去图书馆或者他们是否还有索引卡片)
【讨论】:
【参考方案2】:这样想:当您调用get(key)
时,会计算密钥的哈希码,并据此将数百个中的一个桶指向一个单个 (一组)操作,即您不必搜索所有 100 个即可到达正确的存储桶(在这种情况下,这将是一个线性操作)
【讨论】:
【参考方案3】:这是我的两分钱,朋友。以您认为数组的方式来考虑 HashMap。事实上,它是一个数组。如果我给你索引 11,你不必遍历数组来查找索引 11 处的对象。你只需直接去那里。这就是 HashMap 的工作原理:诀窍是使索引与值相同——本质上。
这就是哈希码的用武之地。让我们看一下哈希函数是单位乘数(即 1)的简单情况。然后,如果你有 0 到 99 的值并且你想将它们存储在一个数组中,那么它们将分别存储在索引 0 到 99 处。因此 put 和 get 显然是 O(1),因为获取和放入数组是 O(1)。现在让我们想象一个不那么简单的散列函数,比如 y = x+2。所以在这种情况下,值 0 到 99 将存储在索引 2 到 101 处。这里给定一个值,比如 11,您必须计算散列以找到它或放入它(散列为 11+2 =13)。好吧,哈希函数正在做一些工作来计算给定值的正确索引(在我们的例子中 y = x+2= 11+2=13)。但是,这项工作所付出的努力与您拥有多少数据点无关。如果我需要放置 999 或 123,则单个 put 或 get 的工作量仍然相同: y= x+2:我每次执行 put 或 get 时只需添加两个:这是恒定的工作。
您的困惑可能是您想一次放置 n 个值。那么在这种情况下,每个看跌期权仍然是恒定的c
。但是c
乘以 n 是c*n
=O(n)。因此, n 与 put 本身无关,而是您同时进行 n 个 put。我希望这个解释会有所帮助。
【讨论】:
我想我缺少的是,哈希码如何为我提供数组中的确切位置?哈希码值是否从字面上指向 JVM 内存中数组的索引?所以: String s = new String("hi"); System.out.println(s.hashCode()); //打印出 97730 所以有一个索引号为 97730 的数组,该数组的值是存储桶(存储桶中的项目列表),如果我将它放入集合中,该对象将被存储在哪里?跨度> 这完全正确:元素的值被转换为所需的索引。哈希函数可以将单词“love”转换为索引 1,将单词“pencil”转换为索引 2,依此类推。当散列函数将两个不同的单词散列到同一个索引/地址时,我们称之为冲突。例如,如果您的哈希函数为“book”返回 3,为“cars”返回 3,那么这就是冲突。如果您还有其他问题,请告诉我。 还要注意,避免碰撞实际上是不可能的。所以一个好的散列函数,所谓的“通用散列函数”,使用双重散列:首先你散列找到一个对象的索引;如果最终发生冲突,您可以使用另一个散列函数来解决冲突,而不是使用列表。 @Konsol Labapen 如果哈希(通常是 32 位整数)是数组的索引,则数组的长度为 2^32。即使每个元素都有一个字节,数组的总大小也会超过 4 GB。但是一个 hashmap 并不是每次都分配 4 GB。那么它是如何工作的呢?以上是关于Java HashMap 如何为“get”操作执行恒定时间查找 O(1)?的主要内容,如果未能解决你的问题,请参考以下文章