HashMap的底层结构
在JDK7中HashMap是通过“数组+链表”实现的,而JDK8中是通过“数组+链表+红黑树”实现的。数组作为哈希运算后的地址,链表和红黑树是为了解决哈希冲突,将哈希运算后相同的元素放到同一个哈希桶中。
HashMap的扩容机制
HashMap数组的初始容量是16,扩容时以2的倍数扩容。是否要扩容由扩容因子决定,默认的扩容因子是0.75,当数组中的元素个数达到容量的0.75就会进行扩容。每个数组中的链表长度达到8时,就会将链表转换为红黑树,在转换前还会检查数组大小是否达到64,如果没有会先扩容数组。在红黑树元素减少时,数量达到6才会退化为链表,这是为了避免频繁对哈希桶插入和删除的操作时链表长度在7和8之间变化,需要不断在链表和红黑树直接转换,消耗性能。
扩容过程:假设hash算法是用key对数组大小取模。在数组扩容后,需要对表中的元素rehash,rehash后哈希桶中的元素会被分散到两个桶中,所以在数组大小小于64时进行扩容能使链表长度减小。
JDK8中对扩容过程的优化:
用位运算替代取模运算,用位运算提升了性能。
在重新计算hash值后,n变成2倍,n-1的mask的高位多了1。从上图可以看到,由于key1和key2的hash值的高位的值(这里为第5位)不同,两个值会被分配到两个哈希桶中。
HashMap的put
JDK7用的是头插法,JDK8是尾插法,原因是用头插法在并发环境下,数组扩容可能会出现环。尾插法只能解决成环的问题,put的方法是没有加锁进行并发控制的,并发下的读写问题依然存在。