为什么HashMap调整大小如果发生碰撞或最坏情况

时间:2017-06-25 11:16:40

标签: java algorithm data-structures hashmap

我问这个问题关于java版本只有1.7。我正在使用反射来找出HashMap的当前容量。在下面的程序中我将12个独特的人放入一个HashMap桶中(使用相同的哈希码)。然后我将第13个独特的人放在相同或不同的桶上(使用相同或不同的哈希码)。在添加第13个元素后的两种情况下,HashMap调整为32个桶。据我所知,由于加载因子.75和初始容量16,HashMap调整为第13个元素的两倍。但是仍然有空桶可用,只有2个桶用于这些第13个元素。

我的问题是:

1)我的理解是否正确。我没有犯任何错误。这是HashMap的预期行为。

2)如果所有这些都是正确的,那么即使有12或11个空闲桶,为什么在这种情况下需要将HashMap与第13个元素加倍。调整HashMap的大小不是额外开销或成本高昂。在这种情况下,需要将HashMap加倍是什么?根据哈希码,第13个可以放在任何可用的桶中。

public class HashMapTest {
    public static void main(String[] args) throws NoSuchFieldException,
            SecurityException, IllegalArgumentException, IllegalAccessException {
        HashMap<Person, String> hm = new HashMap<Person, String>();
        for (int i = 1; i <= 12; i++) {
            // 12 Entry in same bucket(linkedlist)
            hm.put(new Person(), "1");
        }
        System.out.println("Number of Buckets in HashMap : "+bucketCount(hm));
        System.out.println("Number of Entry in HashMap :  " + hm.size());
        System.out.println("**********************************");
        // 13th element in different bucket
        hm.put(new Person(2), "2");
        System.out.println("Number of Buckets in HashMap : "+bucketCount(hm));
        System.out.println("Number of Entry in HashMap :  " + hm.size());
    }
    public static int bucketCount(HashMap<Person, String> h)
            throws NoSuchFieldException, SecurityException,
            IllegalArgumentException, IllegalAccessException {
        Field tableField = HashMap.class.getDeclaredField("table");
        tableField.setAccessible(true);
        Object[] table = (Object[]) tableField.get(h);
        return table == null ? 0 : table.length;
    }
}

class Person {
    int age = 0;
    Person() {
    }
    Person(int a) {
        age = a;
    }
    @Override
    public boolean equals(Object obj) {
        return false;
    }
    @Override
    public int hashCode() {
        if (age != 0) {
            return 1;
        } else {
            return age;
        }
    }
}

输出

Number of Buckets in HashMap : 16
Number of Entry in HashMap :  12
**********************************
Number of Buckets in HashMap : 32
Number of Entry in HashMap :  13

3 个答案:

答案 0 :(得分:4)

  1. 是的,这是预期的行为。
  2. HashMap不关心使用多少桶。它只知道已经达到了载荷因子,并且碰撞的概率因此变得太大,因此应该调整地图的大小。尽管已经发生了许多碰撞,但调整地图大小实际上可以解决这个问题。不是在你的情况下,因为你故意选择相同的hashCode,但在更现实的情况下,hashCodes应该有更好的分布。如果您故意选择错误的hashCodes,HashMap无法做任何事情来提高效率,并且没有必要增加处理极端情况的复杂性,这绝不应该发生,并且HashMap无论如何都无法修复。 / LI>

答案 1 :(得分:2)

是的,您观察到的行为是预期的行为。

HashMap的实现要求您使用合理的hashCode密钥。它假定您的hashCode将在可用存储桶中尽可能均匀地分配密钥。如果你没有这样做(就像在你的例子中所做的那样 - 所有的键都具有相同的hashCode),你将会得到糟糕的表现。

在均匀分布的假设下,一旦传递加载因子,HashMap将其大小加倍是有意义的。它不会检查实际上有多少桶是空的(因为它无法知道是否将新条目分配给空桶或占用桶)。它只检查每个桶的平均条目数。一旦该数量超过负载系数,桶的数量就会翻倍。

答案 2 :(得分:0)

这里也有一个轻微的方面;当您调整内部数组的大小(从16到32)时,您也“触摸”所有条目。让我解释一下:

当有16个桶(内部数组大小为16)时,只有last 4 bits决定该条目的去向;想一想%,但在内部实际上是(n - 1) & hash,其中n是桶的数量。

当内部数组增长时,需要再考虑一个比特来决定条目的去向:曾经是4 bits,现在有5 bits;这意味着所有条目都重新散列,它们现在可能会移动到不同的存储桶;这就是调整大小的原因,分散条目。

如果确实想要填补所有“空白”,请指定load_factor 1;而不是默认的0.75;但这具有HashMap构造函数中记录的含义。