网上关于HashMap的博客实在是太多了,我再系统地来讲HashMap肯定没有现有的博客讲的好,所以我决定换一个角度,就从我们日常使用的地方开始,完整的走一遍HashMap的源码,应该会有不一样的收获。
先看下面一段代码
1 | Map map = new HashMap(); |
平时开发中很常见的用法,需要注意的是,由于使用了阿里巴巴规范插件,new HashMap的地方是有如下提示的
记住这个提示
HashMap的初始化
调用HashMap的无参构造函数,可以看到,在无参构造器中,只是仅仅将loadFactor(加载因子)这个属性设置为默认值(0.75f),如果不懂加载因子是什么暂时也没关系,我们继续往下走
map的put
调用map的put方法,会先执行一个hash方法,参数为传入的key,返回的就是key的hash值
1.先判定key是否为null
2.为null则返回0
3.不为null则调用key对象本身的hashCode方法同时异或 hash值无符号右移16位
为什么求出hash值之后还需要将hash值右移16位之后再与本身异或呢?我们带着这个疑问往下继续
1 | final V putVal(int hash, K key, V value, boolean onlyIfAbsent, |
1 | final Node<K,V>[] resize() { |
从上面的代码可以看出,当map中存放的元素超过扩容阈值后,就会通过resize进行扩容,会用一个长度为原数组2倍的来重新存放之前的键值对,如果Map中需要存放的数据比较多时,会频繁的进行扩容,对性能造成一定影响,所以在大概能确定Map中数据量的情况下,需要对Map的容量进行指定,尽量避免频繁的扩容产生的性能损耗。
map的get
1 | public V get(Object key) { |
HashMap数据结构的理解
HashMap中将数组和链表两种数据结合在一起使用,为什么要这样使用呢?我认为主要好处有这几点:
- 数组方便查询,因为能根据索引直接找到元素的内存地址,所以查询很快,但是如果用纯数组的话,查询元素都是随机访问,无法确定某个元素的具体位置,如果数据量很大的情况下,需要全部遍历,复杂度比较高,效率比较差,所以将hash算法和数组结合在一起,元素的数组角标与哈希值产生关联,key一旦确定,数组角标也能确定,查询效率大大提高
- 在某些情况下,不同的hash值与数组长度-1与出来的角标相同,这种情况也叫做碰撞,这种情况下,就会将同一个数组角标上的元素以链表的数据结构存起来,因为这种情况比较少,所以就用利用了链表修改快,查询慢的特点,在链表长度小于TREEIFY_THRESHOLD时,就用链表保存
- 当链表长度大于TREEIFY_THRESHOLD后,为了保证同一个桶中查询的效率,jdk1.8之后,会将链表转换为一棵红黑树,以保证查询的效率
HashMap中数组的长度为什么使用2的幂次方
先看这段代码
1 | ... |
我们知道,HashMap中是保存着链表类型的数组结构,会根据键值对的key的hash值来决定放到哪个桶(即数组的index)中,因为数组的长度是有限的,当不同的hash值的key算出来的index相同时,就会被放到同一个桶中,即发生碰撞,而使用HashMap的意义,就是要减少这种碰撞,使键值对尽量均匀的分布在各个桶中。因此,我们需要将具有不同的hash值的key进行分类。提到分类,可能有同学会想到使用取模,但是jdk的开发者们使用了更加高效的位运算,即使用数组的长度-1 & hash值。例如:
- 当数组长度为16,hash值为100时
1111 & 1100100 结果为 0100,十进制为4,即index=4
- 当数组长度为16,hash值为101时
1111 & 1100101 结果 0101,十进制为5,即index=5
但是,若将数组的长度变为10
- 当hash值为100时
1010 & 1100100 结果为0,index=0
- 当hash值为101时
1010 & 1100101 结果为0,index=0
这种情况下,更容易发生碰撞,使用2的幂次方-1,保证了较低位尽量为1,与hash值与运算时,能更多的体现hash值的特征,保证放置的key的均匀性,提高hashmap的读写性能。因此,HashMap中数组的长度使用2的幂次方
HashMap的扩容机制
当hashmap中键值对的数量达到threshold(即数组长度*加载因子)时,会触发hashmap的扩容。过程如下:
首先是产生新的数组
重新放置原来的键值对到新的数组中
注意:因为原来计算index时是使用的hash&length-1,此处&oldCap,相当于多与了1bit,此时是否为0就决定于hash值上这一bit是否为1,相当于决定新的index的因素还是由key的hash决定,从理论上,还是体现了key的特性,根据了key的hash值来决定index,还是能保证键值对的均匀分布的。