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的。