HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干。
HashMap数组每一个元素的初始值都是Null。
HashMap通过put方法把元素插入到HashMap,
HashMap的默认初始化长度为16(可显式指定),HashMap的默认负载因子为0.75(可显式指定),插入时,当计算出HashMap中的数量达到了16*0.75(12)时,对HashMap的容量进行扩容,扩容为原来的两倍,扩容时建立更大的数组,然后移动数据到相应位置。扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。
插入时,通过一个哈希函数来确定Entry在数组中的插入位置(index),
比如调用 hashMap.put("apple", 0) ,插入一个Key为“apple"的元素:
index = Hash(“apple”) HashMap采用位运算的哈希函数计算index
假定最后计算出的index是2,那么结果如下:
因为HahMap的数组长度是有限的,当插入的Entry越来越多的时候,再完美的哈希函数也会出现哈希冲突的情况,
比如在插入第六个Entry时计算出的index还是2:
HashMap中使用链表解决冲突:
HashMap数组的每一个元素不止是一个Entry对象,也是一个链表的头节点。每一个Entry对象通过Next指针指向它的下一个Entry节点。当新来的Entry映射到冲突的数组位置时,只需要插入到对应的链表即可:
所以说HashMap的数据结构是通过数组加链表实现的。数组和链表的存取速度较快,可以加快查询。
HashMap通过get方法通过key来查找value,
首先会把输入的Key做一次Hash映射,得到对应的index:
index = Hash(“apple”)
由于刚才所说的Hash冲突,查找的index位置有可能匹配到多个Entry,这时候就需要顺着对应链表的头节点,一个一个向下来查找。假设我们要查找的Key是“apple”:
查找时,会先判断该index位置的值是否为链表,不是链表就根据key和key的hashCode来返回值,
为链表则需要遍历直到 key 及 hashcode 相等时候就返回值。
啥都没取到就直接返回 null 。
所以实际查找时,找到index等于2的数组位置的值,因为它是一个链表,对它进行遍历,找到key为apple
的值返回。
在高并发的场景下,两个线程一起对HashMap扩容,会使链表上的引用关系变成:
节点a和b互相引用,形成了一个环,当在数组该位置get寻找对应的key时,就发生了死循环。而且还伴随有数据丢失的问题。
所以在并发的情况,发生扩容时,可能会产生循环链表,在执行get的时候,会触发死循环,引起CPU的100%问题,所以一定要避免在并发环境下使用HashMap。要并发就使用ConcurrentHashmap。