【问题标题】:Why HashMap resize when it hits TREEIFY_THRESHOLD value which is not required?为什么HashMap在达到不需要的TREEIFY_THRESHOLD值时会调整大小?
【发布时间】:2019-11-22 05:12:52
【问题描述】:

我知道 HashMap 在内部是如何工作的。但是,在使用 TreeNode 实现检查 HashMap 代码时,我没有得到增加存储桶大小的目标,但在存储桶大小达到 MIN_TREEIFY_CAPACITY = 64 之前,我没有得到 treeify

注意:我考虑过Map m = new HashMap();,所以默认大小为16。

默认值。

static final int TREEIFY_THRESHOLD = 8;
static final int MIN_TREEIFY_CAPACITY = 64;

HashMap#putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)

我从putVal 方法中提取了几行代码。

else {
    for (int binCount = 0; ; ++binCount) {
        if ((e = p.next) == null) {
            p.next = newNode(hash, key, value, null);
            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                treeifyBin(tab, hash);
            break;
        }
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
            break;
        p = e;
    }
}

所以每当 binCount 达到 7 时,它就会调用 treeifyBin(tab, hash); 现在让我们按照方法 treeifyBin 中的代码进行操作。

HashMap#treeifyBin(Node[] tab, int hash)

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        ....
    }
}

为什么? 在此方法中,首先在IF 中检查当前tab 的大小是否小于MIN_TREEIFY_CAPACITY = 64,然后调用resize()。它在内部将tab 的大小从默认的16 增加到32,并将所有元素转移到新的tab。再次 32 到 64。我认为这是开销或不必要的。

那么这背后的目标是什么?在putVal 中使用TREEIFY_THRESHOLD 检查大小,但在到达MIN_TREEIFY_CAPACITY 之前不执行treeify

【问题讨论】:

    标签: java java-8 hashmap


    【解决方案1】:

    两者,使用比平常更大的树或容量,都是处理冲突的措施。当有多个key映射到同一个bucket时,可以是以下场景之一(或它们的组合):

    1. 键具有不同的哈希码,但映射到同一个存储桶
    2. 密钥具有相同的哈希码但实现Comparable
    3. 密钥具有相同的哈希码并且不实现Comparable

    这两种方法都无法处理第三点。只有建造一棵树才能处理第二个。当我们遇到第一种情况时,扩展表可能会解决问题,如果确实如此,它的优点是仍然提供 O(1) 查找并允许更有效的遍历(仅遍历数组),而树有 @987654324 @查找和遍历效率较低,需要降低树结构。

    问题在于,分析场景,找出适用的解决方案以及扩展表格是否真的有帮助,这本身就需要时间。此外,当单个put 花费分析费用来解除策略时,它不会得到回报,只是为了让下一个put 找到适合另一个键的策略(毕竟,扩大表大小对整个表有影响)。

    因此,使用启发式算法来适应 HashMap 的可能性和典型用例,而不仅仅是单个 put 操作。请注意,对于小表大小,通过扩展解决存储桶冲突的机会更高,表大小为 16 意味着仅使用四位哈希码,而表大小为 32 意味着使用五位,多 25%。

    我想,JDK 团队使用了对现实生活中的应用程序和库进行基准测试的常用方法来找到正确的折衷方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-02-07
      • 2019-12-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-20
      • 1970-01-01
      相关资源
      最近更新 更多