1.HashMap底层是一个数组,数组的每一项是一个链表,新建HashMap的时候会创建一个数组
transient Entry[] table;
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
……
}
数组每一项都是Entry类型,是一个key与value的映射,并有指向下一个元素的指针。
2.HashMap构造函数
- HashMap()
- HashMap(int initialCapacity),如给定的容量不是2的幂次方,则使用比给定值大,并且最接近给定值的2的幂次方代替。
- HashMap(int initialCapacity, float loadFactor)
- HashMap(Map<?extends k,?extends v>m)
3.Resize()方法
扩容场景:在put()操作后,计算当前容器中的元素个数是否达到阈值,即容器容量负载因子,如果达到,那么就进行扩容操作,一般扩容为现在容量的一倍。
操作:首先计算扩容之后的容量,然后新建一个容器,将旧容器中的元素放入其中。元素扩容之后的hash值是原hash值增加一位,如原容器大小为16,则取key hash值的后四位,扩容后容器容量变为32,则取key hash值的后五位,即元素要么在原来的位置,要么在原来的位置在移动扩容长度的位置。而JDK7的hashmap扩容只会把原链表倒置放到现链表的位置。
4.put()方法
put方法如下图所示:
- 查看table是否为空,如果为空则扩容,如果不为空则执行第二步。
- 根据键值key计算hash值,得到要插入的位置i,如果为空,则直接插入,执行第六步,如不为空,则执行第三步。
- 判断当前插入值和第i个位置的首元素的值是否相等,如果相等则覆盖,否则执行第四步,这里相等位hashcode和equals
- 判断第i个位置是否为树节点(红黑树),如果是,则直接在树中插入节点,否则执行第五步。
- 遍历链表,如果链表长度大于8,则将链表转换位红黑树,并在树中插入节点,否则在链表中插入节点,如在遍历过程中发现key存在,则直接覆盖。
- 插入成功后,判断实际存在的键值数是否超过了threshold,如果超过了就执行扩容。
5.HashMap线程安全性
HashMap线程不安全,而ConcurrentHashMap线程安全
总结:
(1) 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。
(2) 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。
(3) HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap。
(4) JDK1.8引入红黑树大程度优化了HashMap的性能。