1 为什么hashMap效率高?
- 数组下标定位快,链表增删快。查询(数组定位,扫描一部分链表),增删是在链表上进行的,所以hashmap的效率高
2 组成
-
HashMap由数组+链表+红黑树(1.8之后加的)组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。
-
JDK8之后,如果哈希表单向链表中元素超过8个,那么单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6个,会重新把红黑树变成单向链表数据结构。
3 HashMap的put 和 get 的实现
3.1 map.put(k,v)实现原理
-
将k v封装到Node(节点)对象中
-
调用k 的hashCode()方法得出hash值
-
通过哈希算法,将hash值转换成数组的下标,如果下标位置没有元素,就把node添加到这个位置上。如果有,就以链表的形式存放,此时会拿着k和链表上的每个节点的k进行equal,如果返回的都是false,将其添加到链表末尾,如果某个返回了true,此节点的value会被覆盖。
3.2 map.get(k)的实现
-
调用k的hashcode计算hash值 通过哈希算法转换成数组的下标
-
利用下标定位,没有链表,返回此位置上的value或者null,有链表,拿着k和链表上的每一个节点进行equals,如果返回的都是null,则get返回的是null。否则,返回对应节点的value。
4 扩容(resize) 什么时候扩容? 最消耗性能的点。
- 当hashmap中的元素个数超过数组(16)大小*0.75(负载因子)时,就会进行数组扩容。
- 在hashmap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。
5 Hash算法的实现(数组下标的算法&为什么数组长度是2的n次密)
-
首先算得key得hashcode值,然后跟数组的长度-1做一次“与”运算(&)。看上去很简单,其实比较有玄机。比如数组的长度是2的4次方,那么hashcode就会和2的4次方-1做“与”运算。很多人都有这个疑问,为什么hashmap的数组初始化大小都是2的次方大小时,hashmap的效率最高,我以2的4次方举例,来解释一下为什么数组大小为2的幂时hashmap访问的性能最高。
-
看上图,左边两组是数组长度为16(2的4次方),右边两组是数组长度为15。两组的hashcode均为8和9,但是很明显,当它们和1110“与”的时候,产生了相同的结果,也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8和9会被放到同一个链表上,那么查询的时候就需要遍历这个链表,得到8或者9,这样就降低了查询的效率。同时,我们也可以发现,当数组长度为15的时候,hashcode的值会与14(1110)进行“与”,那么最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!
-
所以说,当数组长度为2的n次幂的时候,不同的key算得的index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。