一.特点
1.存取无序
2.键和值都可以是null,但是键只能有一个null
3.键位置是唯一的,底层的数据结构控制键
4.jdk1.8之前的数据结构是:链表+数组 ,jdk1.8之后又加上了红黑树来加速查询
5.使用红黑树的阈值是:1.数组的长度大于64. 2链表的长度大于8.
二.HashMap集合底层数据结构
jdk1.8之前的数据结构是:链表+数组 ,jdk1.8之后又加上了红黑树来加速查询。
当创建HashMap集合对象的时候,在jdk8之前,构造方法中创建一个长度为16的Entry[] table 用来存储键值对数据。在jdk8以后不是在HashMap的构造方法底层创建数组,是在第一次调用put方法时创建的数组,Node[] table用来存储键值对数据,类似Entry[] table。
计算流程
HashMap<String, Integer> HM = new HashMap<>();
HM.put("张三", 10);
HM.put("李四", 20);
HM.put("王五", 30);
当向HashMap中添加数据时,会使用String类重写的HashCode()方法计算出哈希值,然后结合数组长度采用某种算法计算出向Node数组中存储数据的空间的索引值。如果计算出的索引空间没有数据(没有哈希冲突),则直接将键值对存储在数组中。
HashMap底层计算哈希值的算法:
1.调用Key对象的hashCode方法计算出key的hashcode
2.hashcode无符号右移16位(>>>,左边补16个0)与原hashcode进行异或操作。(这一步的目的是使映射到Node数组中的键值对分布均匀,但是我不太理解为什么,有大神可以在评论区解答一下)
3.第二步中得到的异或结果与数组长度-1进行按位与运算(&),得到最终的索引位置
其他的可以采用的计算哈希值的方法:平方取中法,伪随机数法,取余法。(方法效率低没有被采用,取余数计算在底层是通过不断减实现,计算比较慢被淘汰。位运算对于计算机来说计算效率高)
HashCode():当两对象是相等的,那么在两个对象中的每个对象调用HashCode方法的返回值都是相同的整数结果。当equals方法被重写时,通常有必要重写hashCode方法,以维护hashCode方法的常规协定(相等的对象必须有相等的哈希值).
HashMap存储过程:
两个键对应的Node索引位置相同(当一个键值对已经存入HashMap),这时比较两键通过hashCode方法得到的hashcode是否相等(在重写了equals和hashCode两方法后,hashcode不相等等同于equals为false),不等则会在Node存储空间位置开辟出一个节点位置存储新来的键值对,并用单向链表形式存储,当链表长度大于8且数组长度大于64时,采用红黑树存储结构优化查询效率。
HM.put("张三", 20);
当重新插入一个"张三"时,则必然会发生哈希冲突,由于String对象值相等则对应的hashcode也相等,此时需要调用String类的equals方法比较两个值是否相等,相等则将后添加的"张三"对应的value覆盖前面的value,体现到例子中为 10->20。不相等的情况下则可判定为新数据,则创建新节点存储即可。(值不同的对象hashcode值是有冲突的可能的,由于hashCode是将地址映射到一个int类型的值,即64位映射到32位,虽然概率比较小但有可能发生碰撞,所以当hashcode发生冲突时继续使用equals方法判断键是否相同是有必要的)
最后关于String类hashCode和equals方法,可类比到其他类:
1.不同值的对象hashcode不一定不相等,equals一定为false
2.相同值的对象hashcode一定相等,equals一定为true
3.hashcode不相等的两对象值一定不相等,equals一定为false
4.hashcode相等两对象值不一定相等,需要equals进一步判断