【问题标题】:Java Hashmap InternalJava Hashmap 内部
【发布时间】:2016-04-20 16:10:43
【问题描述】:

我对 Java HashMap 类毫无疑问。据我了解

 transient Entry[] table;

表格数组将根据hashCode() 的值保存数据。我需要知道这个数组什么时候被初始化。数组长度是基于我们在HashMap初始化时定义的容量还是调用构造函数时没有定义的默认容量16?

哈希码如何缩放到数组索引?比如hashcode值很大,怎么缩放到10、20这样的数组索引?

我读过,当达到阈值时,会发生重新散列。例如,在默认情况下,当容量为 16,负载因子为 0.75 时,则阈值为16*0.75=12。一旦添加了 12 个项目,就会发生重新散列并且容量将增加。这是否意味着table 数组大小增加了?

【问题讨论】:

    标签: java arrays hashmap


    【解决方案1】:
    public HashMap(int initialCapacity, float loadFactor) {
         if (initialCapacity < 0)
             throw new IllegalArgumentException("Illegal initial capacity: " +
                                                initialCapacity);
         if (initialCapacity > MAXIMUM_CAPACITY)
             initialCapacity = MAXIMUM_CAPACITY;
    
         if (loadFactor <= 0 || Float.isNaN(loadFactor))
             throw new IllegalArgumentException("Illegal load factor: " +
                                                loadFactor);
    
         // Find a power of 2 >= initialCapacity
         int capacity = 1;
         while (capacity < initialCapacity)
             capacity <<= 1;
         this.loadFactor = loadFactor;
         threshold = (int)(capacity * loadFactor);
         table = new Entry[capacity];
         init();
     }
    

    上面的代码块解释了如何以及何时填充table

    一旦发生重新散列,它不会增加表数组大小,因为您可以永远声明数组大小;它每次都会使用更新后的大小创建一个新数组:

    void resize(int newCapacity) {
         Entry[] oldTable = table;
         int oldCapacity = oldTable.length;
         if (oldCapacity == MAXIMUM_CAPACITY) {
             threshold = Integer.MAX_VALUE;
             return;
         }
         Entry[] newTable = new Entry[newCapacity];
         transfer(newTable);
         table = newTable;
         threshold = (int)(newCapacity * loadFactor);
     }
    

    【讨论】:

      【解决方案2】:

      由于您的帖子有很多问题,我将列举您的问题作为我回答的一部分。另外,请注意我要离开HashMap's source code for Java 1.8 b132 来回答我的问题。

      1. 问:我需要知道这个数组什么时候初始化。
        答:table 数组仅在数据首次输入地图时才被初始化(例如,put() 方法调用)。它不会作为映射实例化的一部分本身发生,除非调用了复制构造函数,或者映射被反序列化为对象。
      2. 问:数组长度是基于我们在HashMap初始化时定义的容量还是调用构造函数时没有定义的默认容量16?
        答:正确,table 数组的长度基于您传递给构造函数的初始容量。当未指定初始容量并调用默认构造函数时,使用默认容量。
      3. 问:哈希码如何缩放到数组索引?
        答:有关执行此操作的实际代码本身,请参阅implementation of the putVal() method。基本上发生的情况是代码采用非常大的哈希值并使用表的最后一个元素索引执行bitwise-AND。这有效地使用 table 数组随机化键/值对的位置。例如,如果哈希值为 333(以 2 为基数的 101001101),table 数组大小为 32(100000),则最后一个元素索引将为 31(11111)。因此,选择的索引将是 11111 &amp; 101001101 == 01101 == 13
      4. 问:我看过,当达到阈值时,会发生rehashing。 ...这是否意味着表数组大小增加了?
        A:或多或少,是的。当超过threshold 时,将调整表的大小。请注意,通过调整大小,不会修改现有的 table 数组。而是创建一个新的table 数组,其容量是第一个table 数组的两倍。详情请见implementation of the resize() method

      【讨论】:

      • 谢谢乔纳森。我几乎清楚了。只是另一个问题,即与 Q:3 有关。您已经在示例中清楚地解释了如何将哈希码 333 放入 32 长度数组中。所以这意味着任何哈希码值都可以放入 32 长度的数组中。那么调整大小的需要是什么。在索引冲突的情况下,碰撞对象将存储在同一个索引元素内。我对吗?然后在什么基础上调整大小。
      • 好问题。是的,多个元素可以存储在同一个索引中,因为table 数组的类型是HashMap - Node 的内部类型,它引用了可能的next 节点。因此,以这种方式,固定大小的table 可以具有无限数量的条目。当键值对的数量(即HashMap 中的size 字段)超过threshold 值(即table 数组的大小乘以loadFactor)时,通常会触发调整大小。跨度>
      • 所以在调整大小时,对象会重新排列吗?在上面的例子中,当表大小为 32 时,哈希码为 333 的对象将位于第 13 位。在调整大小期间,大小变为 double ,因此现在大小将变为 64。为避免混淆,我们再次考虑将其翻倍为 128,因为大小为 64 时它也占据 13 位。 128、1111111 和 101001101 == 1001101==77。它会存储在 77 位置吗?
      • 关于调整大小,对象可以重新排列,是的。考虑一个大小为 4 的table(最后一个索引是 3,即以 2 为底的 11),索引一个 14 (1110) 的哈希。在这种情况下,该值将存储在索引3 &amp; 14 == 11 &amp; 1110 == 0011 &amp; 1110 == 10 == 2。稍后,table 的大小被调整为 8(最后一个索引为 7,或以 2 为底的 111)。因此,当调整大小时,14 将被索引为7 &amp; 14 == 111 &amp; 1110 == 0111 &amp; 1110 == 110 == 6。因此,table 数组中相同元素的索引在调整大小时会从 2 变为 6。
      • 至于您对get() 方法的关注,节点没有被索引“彼此内部”。相反,您可以将table 视为在每个索引处都有一个节点的链接列表,即使类型是Node[]。这是因为Node 实例包含对next 节点的引用。详见Node类的实现(tinyurl.com/hhqcxl8)。请注意,这称为链接。有关哈希表链接的描述,请查看有关该主题的 Wikipedia 文章 (tinyurl.com/zn8gl3c)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-03-25
      • 2020-06-21
      • 2014-05-18
      • 2018-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多