关于HashMap的总结

来源:互联网 发布:网络割接流程 编辑:程序博客网 时间:2024/06/07 00:10

1.HashMap中的负载因子:
负载因子越大,表明填满的元素越多,则hash冲突机会越大,查找效率低
负载因子越小则数组越稀疏,查找效率高,但是造成空间的浪费。

默认为0.75,如果机器内存紧张,对查找速度没有要求可以将负载因子设置大一些,如果机器内存足够,对查找速度要求高则将负载因子设置小一些。

当发生hash冲突时,新增的Entry中的key若比较为false,则新增的Entry添加到Enrty链表的头部。


2.关于HashMap的初始容量:

初始容量默认为16.
为什么需要设置初始容量:
当put之后Entry数组中的元素超过了临界值(初始容量*负载因子)时,将会进行扩容,扩容的操作如下:

新增一个容量为之前2倍的Entry数组并重新计算每个元素在其中的位置,然后将原数组中的元素复制进新数组

重新计算所有元素索引以及复制数组是很消耗性能的操作,特别是当数据量很大时。因此如果能够预先算出hashmap中的元素个数,则设置初始容量为佳,避免了耗时的扩容操作。

3.关于put的流程

(key为null的话,hash值为0,对象存储在数组中索引为0的位置。即table[0])

向map中put一个键值对的时候,若key为null,将该键值对添加到数组的头部,此处同非空键值对一样,若数组头部下标0的位置中已经存在key为null的键值对,则原键值对会被新的键值对覆盖掉。
若key不为null,则调用indexFor(hash(key.hashcode()))方法计算出应该存放的位置的数组下标,若该数组下标的位置不存在hash冲突,则直接放进去,若存在hash冲突则将新Entry的next指向链表头节点,然后将Entry放进去。

4.关于get()方法

首先根据key计算数组下标的位置,然后遍历链表找到equals结果为true的Entry并返回。

所以当向HashMap中放入自定义的对象作为键时时需要覆盖对象的equals方法和hashcode方法

5.JDK1.8中HashMap的优化

(1)优化计算hash的过程

在上面中说到put方法计算下标的时候,使用了indexFor(key的hash)这个方法,在jdk1.8中,优化了计算hash的过程。先上源码

static final int hash(Object key) {        int h;        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);    }
上面是1.8中计算hash的代码,将key的hashcode高位异或低16位得到hash

这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。
(2)当Entry链表长度大于8时,链表变为红黑树。
所以上面说的遍历链表之前,首先会判断是否是红黑树,如果是,则直接执行插入,如果不是,则开始遍历链表,中途如果发现存在hashcode、equals一样的Entry则替换,若没有,则新的Entry放在头节点。
(3)JDK1.8中,在扩容的时候不需要重新计算hash,只需要看原来的hash和新容量-1的与运算的结果的最高位是否为0即可计算出新的索引:
如果为0,则新索引位置不变,如果为1则新索引位置为原索引位置+老数组的容量。


6.HashMap的get方法的时间复杂度:

假如hash算法非常好,数据分布非常均匀,每个数组下标位置只存在头节点,也就是说链表长度都为1,那么时间复杂度可以达到O(1),但假如算法很差,元素都集中在一个下标的那条链表里面,则查找的时间复杂度则为O(n),当然,这是说的1.7,在1.8中,链表长度超过8时会转换为红黑树,则其查找时间复杂度下降为O(logN),理论上来说1.8的hashmap性能是优于1.7的。