【问题标题】:What if a HashMap is full?如果 HashMap 满了怎么办?
【发布时间】:2014-11-13 08:50:42
【问题描述】:

我知道java Hashmap有一个容量和负载因子参数。所以,如果这个hashmap中的项目数超过容量*负载因子,就会重建一个新的hashmap。我有一些关于重建的问题它:

  1. 如果发生重建,之前的 hashmap 会被回收还是仍在使用?
  2. 既然我们需要更大尺寸的hashmap,那么,hash函数是否会改变?
  3. 对于 ConcurrentHashMap ,如果一个线程正在插入(当然,这个插入操作导致了重新构造)而另一个线程正在读取呢?例如,它将从旧的 hashmap 中读取还是从新的 hashmap 中读取?

【问题讨论】:

  • 您对这些类的内部工作有什么特别的担心吗?您是否遇到过您认为可能与此有关的问题?

标签: java hashmap concurrenthashmap


【解决方案1】:

如果发生重建,之前的 hashmap 会被回收还是仍在使用?

还是一样的hashmap,只是内部存储被重构了。重建后旧的桶数组不再需要,可以 gc'ed。

更新:内部HashMapNode<K,V>[] table。在调整大小期间,将构造一个新数组,移动元素,然后将table 替换为新数组。在该操作之后,地图本身将不再引用旧数组,因此除非没有其他引用(这不太可能,因为 table 是包私有的)它是符合 gc 的。

既然我们需要更大尺寸的hashmap,那么hash函数是否会改变?

不,哈希函数不会改变。它通常不依赖于桶的数量,但生成的哈希将被调整以获得正确的桶(例如通过应用模数)

更新HashMap这样计算桶索引:(size - 1) & hash,hash是key的hashCode()方法的返回值,不依赖于map本身.

对于 ConcurrentHashMap ,如果一个线程正在插入(当然,这个插入操作导致了重新构造)而另一个线程正在读取呢?例如,它将从旧的 hashmap 中读取还是从新的 hashmap 中读取?

我不得不在这里猜测(我稍后会查找代码),但我假设只要线程正在从旧存储桶中读取,它们仍然会被使用并且稍后会被释放。

更新: 我快速浏览了ConcurrentHashMap 源,其中引用了get() 使用的当前table 和可能的目标nextTable用于调整大小操作。在调整大小期间,元素被转移到nextTable,最后table 设置为nextTable,有效地切换表。

这意味着在调整大小期间,旧表仍会被读取,并且在某些时候会被替换。在插入操作期间,可能会出现一些阻塞,例如通过使用同步块,尤其是在需要调整大小或已经执行调整大小时。

文档也暗示了这一点:

一个支持检索的完全并发和高预期更新并发的哈希表

这意味着get 不会阻止,但putremove 等。可能会在某个时候阻止。

【讨论】:

    【解决方案2】:

    From HashMap API

    1)

    HashMap 的实例有两个影响其性能的参数:初始容量和负载因子。容量是哈希表中的桶数,初始容量只是哈希表创建时的容量。负载因子是哈希表在其容量自动增加之前允许达到的程度的度量。 当哈希表中的条目数超过负载因子和当前容量的乘积时,对哈希表进行重新哈希(即重建内部数据结构),使哈希表有大约两倍的桶。

    2) 如果您提前知道大小,最好在构建地图对象时创建 hashbuckets。

    HashMap(int initialCapacity)
    

    【讨论】:

      【解决方案3】:

      如果发生重建,之前的 hashmap 会被回收还是仍在使用?

      如果没有对先前结构的引用,它们可以被垃圾回收。

      既然我们需要更大尺寸的hashmap,那么hash函数会不会改变?

      不完全是。散列仍然是相同的,只是散列映射到存储桶的方式发生了变化。例如,在扩展之前,哈希 27,103 和 32,504 可能会映射到同一个桶。展开后,它们可能会映射到不同的桶。哈希映射将有更多的桶,每个桶中的元素更少。

      对于 ConcurrentHashMap ,如果一个线程正在插入(当然,这个插入操作导致了重新构造)而另一个线程正在读取怎么办?例如,它会从旧的 hashmap 中读取,还是从新的 hashmap 中读取?

      如果你在乎,那么你必须使用锁。你使用 ConcurrentHashMap 是因为你想要并发。如果您想在读取和写入之间进行可预测的排序,则必须对它们进行排序。即使没有重建也是如此。如果您同时进行读取和写入,则读取可能会或可能不会反映写入。如果您在一个线程中进行写入并在该写入返回之前从另一个线程发出读取,则读取可能会或可能不会反映写入。 ConcurrentHashMap 为您提供合理的结果,但不强制执行排序(怎么可能?)。

      【讨论】:

        【解决方案4】:
        1. previous 将被垃圾回收
        2. 散列函数不会改变,但散列值(桶)可能会因为散列函数通常取决于散列映射的大小
        3. 哈希图不是线程安全的。如果 hashmap 在线程之间共享,请使用 ConcurrentHashMap

        【讨论】:

          【解决方案5】:

          随着HashMap中元素数量的增加,容量也随之扩大。负载因子是决定何时增加 Map 容量的度量。默认负载系数为容量的 75%。

          当Map中的item数量超过阈值限制时,Map的容量翻倍。当容量增加时,我们需要将所有条目(包括现有条目和新条目)平均分配到所有存储桶中。在这里,我们需要重新散列。即对于每一个现有的键值对,以增加的容量为参数,再次计算哈希码。(Rehashing)

          对于第三个问题,您应该使用 concurrenthashmap 来防止错误发生。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-11-14
            • 1970-01-01
            • 2011-06-14
            • 2012-08-01
            • 1970-01-01
            • 1970-01-01
            • 2016-12-19
            • 1970-01-01
            相关资源
            最近更新 更多