简单对HashMap进行剖析,如果有歧义或者错误之处,还请大家多多指教。
一,首先我主要从这几个方面讲解HashMap:
1.hashMap工作原理?
2.hashMap的put/get实现原理?
3.hashMap线程安全?
4.hash实现?
5.如果hashMap的大小超过了负载因子定义的容量,会怎么处理?
二.
Hash table based implementation of the Map interface. This
implementation provides all of the optional map operations, and permits
null values and the null key. (The HashMap
class is roughly equivalent to Hashtable , except that it is
unsynchronized and permits nulls.) This class makes no guarantees as to
the order of the map; in particular, it does not guarantee that the order
will remain constant over time.
这段话摘自hashMap源码。
1.总结几点,基于Map接口实现,允许 键值为null、非同步、不保证顺序、并且不保证随时间变化顺序变化。
2.hashMap中有两个关键变量
一个是 load factor(负载因子),默认为0.75。另一个是capacity(容量)。
capacity就是散列桶的大小,当entries数量大于capacity*load factor这个程度时,就扩充为原来的2倍。
为什么capacity容量是 扩充为原来的2倍?
因为取模使用位运算会比较快一些(hash&(arrayLength-1)),所以就会一直是2的n次幂。
如果你想提高HashMap的迭代效率,请不要设置过大的capacity(默认为16),也不要过小的的设置load factor负载因子(默认0.75)。
3.首先 我们要确定一个事情 。entry数据结构是什么?entry(hash,key,next,value)。
三.put 原理
1.首先通过key的hashCode()得到hash,然后通过hash计算出索引值index。
2.如果index没有发生碰撞,那么直接插入buckets里面。
3.如果index发生了碰撞,那就插入到bucket的后面,以链表的方式存储。
4.如果节点已经存在key,那么替换oldValue;
5.当链表存储长度太大,会自动变成红黑树;
6.如果插入时,buckets满了,HashMap的容量(超过了 capacity*load factory)会resize,进行扩充容量(扩充为原来的2倍)。
感兴趣的同学可以看看源码,是一个样的意思。
四.get原理
1.取bucket第一个节点,直接命中。
2.若没有直接命中,
如果发生碰撞,通过k.equals()方法可以entry。
如果是链表,那么通过k.equals()查找,时间复杂度为O(n);
如果是红黑树,那么通过k.equals()查找,时间复杂度为O(logN);
五.hash的实现
1.先通过hashCode()计算hash,然后计算出下标index。
源码中 是通过(n-1)&hash计算出下标的,因为会存在频繁发生碰撞事件,所以设计人员在计算index之前,先把hash值的高16bit和低16bit进行了一次异或(避免了因为高16bit没有参加运算导致频繁碰撞),这样避免了发生频繁碰撞,还减少了系统开销,如果碰撞依然频繁,就会用树解决。
2.在jdk1.7及之前,当发生了碰撞,会以链表形式存储,但是时间复杂度为O(1)+O(N),一旦N特别大,查找效率非常低。
在jdk1.8及之后,进行了优化处理,默认链表阈值为8,当超过这个阈值时,就会以红黑树进行存储,这样的时间复杂度为O(logN)+O(1),提高了效率。
六.resize
当bucket容量需要扩充为原来的2倍时,里面的元素要么原位置,要么重新计算hash得到新位置。因为容量扩大为原来的2倍,所以每个元素也就新增了一个bit位,我们只需要判断这个bit位是0或者1就可以了。
这个0或者1是随机的,
当bit位为0时,保留原位置不变;
当bit位为1时,newIndex= 原位置+原容量大小(默认是16)。
更多详细内容请查看源码。下期再见!