HashMap的最详细介绍(一)HashMap的介绍,特点,存储方式以及hashCode()的用法
HashMap是基于哈希表的Map接口实现,是以key-value存储形式存在的,即主要用来存放键值对,HashMap的实现不是同步的,这意味着它不是线程安全的,它的key,value都可以为null,此外,HashMap中的映射不是有序的。
在JDK1.8之前,HashMap由数组+链表组成,数组是HashMap的主体,链表则是为了解决哈希冲突(两个对象调用的hashCode方法计算的哈希码值一致,导致计算的数组索引值相同)而存在的。hashCode方法是用于返回字符串的哈希码,这个在后面的例子中会进行解释。
JDK1.8之后,在解决哈希冲突时有了较大的变化,当链表长度大于阈值(或者红黑树的边界值,默认为8),并且当前数组长度大于64时,此时,此索引位置上的所有数据改为红黑树进行存储。 补充一下:将链表转换为红黑树之前会进行判断,即使阈值大于8,但是数组长度小于64,此时并不会将链表变为红黑树,而是进行数组扩容。这样做的目的是因为数组比较小,需要尽量避开红黑树结构,因为在这种情况下,变为红黑树结构,反而会降低效率,因为红黑树需要左旋,右旋,变色这些操作来保持平衡,同时数组长度小于64时,搜索时间相对要快些。
下图为HashMap的示意图:
具体操作的实现,会从后面的实例中展示。
因此归结出HashMap的特点如下:
1.存取是无序的
2.键和值都可以为null,但是键位置只能是一个null
3.键位置是唯一的
4.JDK1.8之前:数组+链表 JDK1.8之后:数组+链表+红黑树
5.阈值>8且数组长度>64,才将链表转换为红黑树,目的是为了更高效的查询
接下来介绍一下HashMap集合底层的数据结构:数据结构是计算机存储,组织数据的方式,数据结构是指相互之间存在一种或多种特定关系的数据元素的集合,精心选择的数据结构可以带来更高的运行或者存储效率,数据结构往往同高级的检索算法和索引技术有关。
HashMap<String,Integer> hm = new HashMap<>();
当创建HashMao集合对象时,在jdk8之前,构造方法中会创建一个长度为16的Entry[] table来存储键值对,在jdk8之后,不是在HashMap的构造方法底层创建数组,而是在第一次调用put方法时创建的数组Node[] table来存储键值对数据。
1.假设向哈希表中存储JIDA-28数据,那么根据JIDA调用类中的重写之后的hashCode()方法计算出值,然后结合数组长度采用某种算法(比如说取余)计算出向Node数组中存储数据的空间的索引值,如果计算出的索引空间没有数据,则直接将JIDA-28存储到数组中,假设索引值为3 。
hm.put("JIDA",28);
这里也举出几个面试HashMap的面试题: 哈希表底层采用何种算法计算hash值?还有哪些算法可以计算出哈希值? 答:底层采用的是key的hashCode方法的值结合数组长度进行无符号右移,按位异或,按位与计算出索引还可以采用平方取中法,取余法,伪随机数的方法。
HashMap<String,Integer> hm = new HashMap<>();
hm.put("JIDA",28);
hm.put("XINYU",18);
hm.put("ZHANGSAN",38);
hm.put("JIDA",29);`
3. 在步骤2的基础上继续向哈希表中存储数据ZHANGSAN-38,假设计算出的hashCode方法结合数组长度计算出的索引值也是3,那么此时数组空间不是null,此时底层会比较JIDA和ZHANGSAN的hash值是否一致,如果不一致,则在此空间上划出一个结点来存储键值对数据,这种方法称为拉链法。
4. 假设向哈希表中存储数据JIDA-29,那么首先根据JIDA调用hashCode方法结合数组长度计算出的索引值肯定是3,此时比较后存储的数据和已经存在的数据的hash值是否相等,如果hash值相等,此时发生hash碰撞,也称为hash冲突。
那么此时底层会调用JIDA所属类String中的equals方法来比较两个内容是否相等:
1.相等:将后添加的数据的value覆盖之前的value
2.不相等:那么继续向下和其他数据的key进行比较,如果都不相 等,则画出一个结点来存储数据
举个equals方法相等的例子:如图所示JIDA-29覆盖JIDA-28
调用hashCode方法结合数组长度计算的索引值相同的情况下,hash值也相同的情况下,equals也可能不相等,举个例子:例如,重地和通话,即使hashCode()相同,equals却不满足。
大家也可以关注我的公众号,会更新Java语言以及数据结构算法、软件安装更新的电脑知识:琢磨先生DataBase