(一)HashMap系列:负载因子0.75
(二)HashMap系列:树化阀值8,退化阀值6
(三)HashMap系列:2次方扩容
(四)HashMap系列:put元素(不看完将后悔一生!)
一、前言
HashMap在JDK7与JDK8有些不同,主要是在 hash 冲突后,数据结构的改变:
本篇的主题是负载因子(0.75),因此是针对数组来分析,为何是0.75这个值!
二、负载因子的作用
在HashMap中,所有的对象都存储在类型为 Node 的变量名为 table 的数组中:
transient Node<K,V>[] table;
该数组初始大小(capacity = 16)。
当 hash(key) 算出来的值,与已存在的发生冲突时,会在冲突的结点后添加新的结点,此时就变成了链表。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
每个 Node 对象初始时的 next 为 null ,只有在冲突时,next 才会指向下一个相同 hash(key) 但 key 不同的新的 Node 对象。
当链表长度超过 8 时,将转成『红黑树』;当『红黑树』的个数低于 6 时,会重新转化为链表;
随着 table 中的数据越来越多,冲突会越来越频繁,就会影响 HashMap 的执行效率,无非时间或者空间,那到底是如何影响的呢?
2.1、负载因子 = 1.0
负载因子为1.0时,意味着,只有当 table 全部被填满,table 才会扩容;当 table 中的元素越来越多时,会出现大量的冲突:
- 若此时是链表,则查询效率是 O(n),即线性;(插入/删除结点也要遍历到链表);
- 若此时是红黑树,查询效率是 O(logN),但红黑树的插入与删除就异常复杂,每次都要调整树;
因此,负载因子1.0,实际是牺牲了时间,但保证了空间的利用率。
2.2、负载因子 = 0.5
负载因子为0.5时,意味着,当 table 中的元素达到一半时,就发生扩容,table 容量扩大一倍:
- hash 冲突减少;
- 链表长度不会很长;
- 即便链表长度超过8时转成红黑树,树的高度也不会很高;
查询效率提高了,但这里,我们发现,会有大量的内存浪费,因为数组中的个数永远小于数组长度的一半。
因此,负载因子0.5,实际是牺牲了空间,但保证了时间效率。
2.3、负载因子 = 0.75
经过上面的分析,我们发现 [0.5 , 1.0] 对应着 [时间极端, 空间极端],那我们就需要找一个平衡点,这个点就是合理的负载因子值。
源码中有解释:
其意思为:负载因子0.75较好的平衡了时间和空间成本,因子太高增加了查询成本。