在java中,HashMap 或者HashTable,它们的键值被放满了,会出现什么情况?

        这种问题最好的解决方案就是看文档和源码!在目前的JDK实现中HashMap和Hashtable是很相似的(The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) 。所以仅以HashMap为例(jdk1.7.0_51):
下面是比较常见的构造方法三种方法(Java 7 docs),
HashMap()
Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75).
HashMap(int initialCapacity)
Constructs an empty HashMap with the specified initial capacity and the default load factor (0.75).
HashMap(int initialCapacity, float loadFactor)
Constructs an empty HashMap with the specified initial capacity and load factor.
       这里指明了两个对于理解HashMap比较重要的两个参数 int initialCapacity, float loadFactor,这两个参数会影响HashMap实例的效率,我们知道HashMap底层采用的开散列数组实现,利用initialCapacity这个参数我们可以设置这个数组的大小,也就是散列桶的数量,但是如果需要Map的数据过多,在不断的add之后,这些桶可能都会被占满,
 
      这是有两种策略,一种是不改变Capacity,因为即使桶占满了,我们还是可以利用每个桶附带的链表增加元素。但是这有个缺点,此时HaspMap就退化成为了LinkedList,使get和put方法的时间开销上升,这是就要采用另一种方法:增加Hash桶的数量,这样get和put的时间开销又回退到近于常数复杂度上。但是什么时候需要增加Capacity呢?等到所有的桶恰好占满?这样的话不是不行,还是因为效率问题!
 
       所以JDK采用了预处理的方法,这时loadFactor就派上了用场,当size>initialCapacity*loadFactor时,Java虚拟机就在内部实现了新的rehash,重新扩充hash桶的数量,在目前的实现中,是增加一倍,这样就保证当你真正想put新的元素时效率不会明显下降

       所以其实一般情况下HashMap并不存在键值放满的情况。当然并不排除极端情况,比如设置的JVM内存用完了,或者这个HashMap的Capacity已经达到了MAXIMUM_CAPACITY(目前的实现是2^30),但是我们先不考虑这种情况,说说initialCapacity和loadFactor这两个参数如何设置比较好,initialCapacity的默认值是16,有些人可能会想如果我的内存足够的话,我是不是可以将initialCapacity设的大一些,即使实际用不了这么大的空间,这样的话就可以避免可能的rehash导致的效率的下降,反正无论initialCapacity大小,我们使用的get和put方法都是常数复杂度的。
 
       这么说没什么不对,但是可能会忽略一点,我们实际的程序中可能不仅仅使用get和put方法,也有可能使用迭代器(特别是一些库开发中),如果initialCapacity容量比较大,那么会使迭代器的效率大大降低。所以最为理想的情况还是在使用HashMap前估计一下你的数据量。那么loadFactor这个参数设什么样的值好呢?这个值的默认值是0.75,这是JDK开发者权衡时间和空间效率之后得到的一个相对优良的数值。如果这个值射的过大,虽然空间利用率是高了,但是对于HashMap中的一些方法的效率就下降了,包括get和put方法,至于原因我之前已经说过了,这会导致每个hash桶所附加的链表增长,影响存取效率。如果这个值比较小的话除了导致你的空间利用率较低外没有什么坏处,只要你有的是内存,毕竟现在大多数人把时间看的比空间重要。但是实际中还是很少发现有人会将这个值设置的低于0.5.

下面说说刚才谈到的两种极端情况,如果一开始设置的initialCapacity很大乃至超过了MAXIMUM_CAPACITY,这时jdk仅仅是用MAXIMUM_CAPACITY代替initialCapacity进行初始化,下面附上构造函数的源码:
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);
        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }
或者不断的rehash导致capacity变大,我们查看resize方法可以知道一旦超过了MAXIMUM_CAPACITY,jdk仅仅是将 threshold 改为 Integer.MAX_VALUE
 void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
}

那如果最后超过了Integer.MAX_VALUE怎么办?我估计是jvm会自动根据之前分配的内存继续增大threshlod,当然抛出一个异常也未尝不可。

来源:https://www.zhihu.com/question/19673196/answer/35491079

 

posted @ 2021-03-02 17:40  元小疯  阅读(397)  评论(0编辑  收藏  举报