摘要HashMap的原理也是大厂面试中经常涉及到的问题,也是工作中常用的Java容器。本文主要针对以下几个问题进行分析和解释,帮助大家理解HashMap的原理。在HashMap中添加键值对的过程是怎样的?为什么HashMap不是线程安全的?为什么要一起重写hashCode()和equal()方法?1、在HashMap中添加键值对的过程是怎样的?这是网上找的流程图。可以结合步骤看看这个流程图,了解添加键值对的过程。1、初始化表,判断表是否为空或null,否则执行resize()方法(resize方法一般在扩容时调用,初始化表时也可以调用)。2.计算哈希值根据键值key计算哈希值。(因为hashCode是int类型的变量,是4字节32位,所以这里会把hashCode的低16位和高16位进行异或,保留高位的特性,这样得到的hash值更准确均匀分布)3.插入或更新节点根据(n-1)&hash计算得到插入的数组下标i,然后判断table[i]==null,则说明该节点下没有hash冲突元素当前数组下标,直接添加新节点。table[i].hash==hash&&(table[i]==key||(key!=null&&key.equals(table[i].key)))判断table[i]的第一个元素是否为thekey相同,如果相同,直接更新值。table[i]instanceofTreeNode判断table[i]是否为树节点,即table[i]是否为红黑树,如果是红黑树则直接向树中插入键值对.其他情况不满足上述判断条件,说明table[i]存储了一个链表,然后遍历链表,判断已有元素的key是否与插入的键值对的key相等,如果是,则更新值,如果否,则在链表的末尾插入一个新节点。插入后判断链表长度是否大于8,大于8则将链表转为红黑树。4、扩容插入成功后,判断实际键值对大小是否超过最大容量阈值(通常为数组长度*负载因子0.75),如果超过则扩容。源码如下:2、HashMap为什么不是线程安全的?其实通过学习在HashMap中添加键值对的方法,我们可以看到整个方法中并没有使用锁,所以一旦多行并发访问,就可能会损坏数据。不一致,例如:如果有两个添加键值对的线程,都执行到if((tab=table)==null||(n=tab.length)==0)这一行,两者都是tablevariables数组初始化会导致初始化的数组表被覆盖,然后之前初始化的线程会将键值对添加到之前初始化的数组中,导致键值对丢失。3、为什么要把hashCode()和equal()方法一起重写?当我们的对象作为HashMap中的键或者HashSet中的元素时,我们必须同时重写hashCode()和equal()方法。查看hashCode()和equal()方法的默认实现,可以看到Object类中的源码如下。可以看到equals()方法的默认实现是通过判断两个对象的内存地址是否相同来判断返回结果。网上很多博客都说hashCode的默认实现是返回内存地址,其实是错误的。以OpenJDK为例,hashCode默认有五种计算方式,有的返回随机数,有的返回内存地址。具体计算方式取决于运行时库和JVM的具体实现。有兴趣的朋友可以看看这篇博客blog.csdn.net/xusiwei1236...再看看hashCode()方法,equal()方法在HashMap中的应用为了均匀存储一组key-valuepairs在一个数组中,HashMap计算key的hashCode得到一个hash值,用hash对数组长度取模,得到数组下标,将键值对存入数组对应的链表中下标(假设链表长度小于8,未达到则转为黑树的红色阈值)。下面是添加键值对的putVal()方法。当数组下标对应一个链表时,执行的代码可以清楚的看到,判断添加的key是否等于链表中已有的key的方法主要是e.hash==hash&&((k=e.key)==key||(key!=null&&key.equals(k))),即:1.先判断hash值是否相等,不相等直接结束判断。因为hash值不相等,key肯定不相等。2、判断两个key对象的内存地址是否相等(equal指向内存中的同一个对象)。3.如果key不为null,则调用key的equal()方法判断是否相等,因为有可能两个key在内存中存储的地址不同,但是相等。就像背景假设我们有一个KeyObject类。假设我们认为两个KeyObject的属性a是相等的,那么这两个KeyObject是相等的,相等的。我们使用KeyObject作为HashMap的key,以KeyObject是否相等作为去重标准,不能重复添加KeyObject相等,value不等于HashMap。假设hashCode()方法和equals()方法都不重写(结果:HashMap不能保证去重)执行如下代码:如果KeyObject的hashCode()方法和equals()方法都不重写,那么即使属性KeyObject的a为1,key1和key2的hashCode不同,key1和key2调用的equals()方法不相等,这样key1和key2就可以同时存在于hashMap中了。打印结果:如果只重写hashCode()方法(结果:不能正确判断为等于链表的元素,所以不能保证去重)执行如下代码:此时执行equal()方法是默认实现,即当两个对象的内存地址相等时,equal()方法返回true。key1和key2虽然a属性相同,但是在内存中是不同的对象,所以key1==key2的结果会是false,KeyObject的equals()方法默认实现是判断内存地址两个对象的,所以key1.equals(key2)也会为false,所以这两个键值对可以重复添加到hashMap中。输出结果:如果只重写equals()方法(结果:映射到HashMap中不同的数组下标,不能保证去重)。假设只有equals()方法,hashCode方法将是默认实现。具体的计算方式取决于JVM。(测试时发现内存地址不同但相等的对象,它们的hashCode不同),所以计算出来的数组下标是不一样的,会存储到hashMap中不同数组下标下的链表中,这也会造成重复元素的存在。输出结果如下:总结所以当我们的对象作为HashMap中的key或者HashSet中的元素时,我们必须同时重写hashCode()和equal()方法,因为hashCode会影响存储的数组下标在key和链表中的元素进行初步判断,equal()是判断key是否等于链表中key的最终标准。因此,只重写hashCode()方法会导致无法正确判断与链表元素是否相等,从而无法保证去重)只重写equals()方法会导致键值对为映射到HashMap中不同的数组下标,不能保证。重的
