由浅入深谈HashMap(一)
谈起hashmap,很多人都会觉得很简单嘛,会用不代表简单,不信?往下看:
猜猜上述代码的运行结果为多少?结果到底为两个张三,还是一个张三呢?
以上代码的运行结果为:
结果是一个张三,且值为第二个张三的值。
别急,再往下看看:
当map的key为对象时,hashCode方法返回值是age的hashCode值时,map集合的长度为多少呢?答案是2
当hashCode方法返回值是name的hashCode值时,map集合的长度为多少呢?答案是2
想知道原因,那我们先开始学习HashMap吧。
HashMap基于hash表实现map接口,允许null的值和null的键,且不保证结果的顺序性。如下所示代码:
根据数组查询快,链表增删快的特点,hashmap的数据结构在JDK1.8之前是数组+链表,在JDK1.8后是数组+链表+红黑树,如下图所示:
在数据量比较小的时候,冲突较小,链表进行增删操作比较快,但数据量足够大,冲突大的时候,为了提高链表的查询效率(链表不支持随机访问,查询效率为O(n)),红黑树用到了旋转操作,使得红黑树在删除,插入操作时,保证了二叉查找树的平衡,从而获得较高的查询性能,查询的时间复杂度为O(logn)。
HashMap最常用的是put方法与get方法。
Put方法:
例如:hashMap.put(“zhangsan”,23);插入zhangsan这个元素,确定这个元素存放的索引位置的过程如下图所示:
由上图我们可以知道,在文章开篇涉及到的代码,在比较两个对象中的属性值是否相等时,覆盖了hashCode方法与equals方法,利用hashCode方法,计算出属性的hashCode值,当hashCode值相等时,再比较equals方法,上图代码,age不同,所以经过equals方法比较出来后,不是同一对象,put 方法的key不相等,map集合的长度为2.
如上图所示,根据hash方法计算出hash值作为数组的下标,那么当出现计算出来的hash值有相等的情况的时候,也就是发生冲突了,如下,该怎么解决呢?
HashMap,从字面意思也可以知道,跟hash有关,hash是一种算法,那么HashMap是不是利用了hash的算法来解决冲突呢,是用了什么样的算法来解决冲突的呢?
HashMap是利用了哈希表来存储的,为了解决冲突,有两种常用的方式:开放定址法,链地址法。java的HashMap采用的是链地址法,可以理解为数组+链表的形式,开辟出一维的数组空间S[Length],对每一个计算出的hash值,将hash值为i的元素都存储到头指针为s[i]的链表中,新来的小伙伴插入到链表中,采用的是“头插法”,而不是“尾插法”的原因是:采用所谓“尾插法”时,新来的小伙伴为了找到自己的位置,需要从链表的第一个元素开始遍历,找到最后一个元素后,再插入到末尾,时间复杂度为O(n),而采用头插法,新来的小伙伴只要站到队头的位置,不需要从头遍历,时间复杂度为O(1)。如上,hashcode利用hash算法的高位运算,取模运算计算出hash值,也就是说,hash算法计算出的hash值越分散均匀,hash冲突的概率就会越小。
别小看一个简单的put方法,里面可以看出很多的名堂出来的,下篇将会详细分析put方法的源码。
如果有关注put方法源码的小伙伴,肯定有注意到put方法里面有一个resize方法,这个方法就是用来做扩容的。
扩容(resize):扩容从字面意思理解就是扩大容量,扩容并不是说的是自动扩大空间,而是使用一个空间更大的桶,将原来空间小的桶的数据,重新装到大的桶。
那么什么情况下需要扩容呢,首先说说在hashmap源码里出现的几个属性:
int threshold; :所能容纳的key-value对阈值
final float loadFactor; : 负载因子(根据对时间空间之间的权衡,默认值为0.75
int modCount; :hashmap内部结构发生变化的次数
int size; :hashmap中实际存储的键值对的数目
initialCapacity; :初始容量
Threshold =capacity*loadFactor,所以逆向思考下,当有人冷不丁问你一句:
Map val = new HashMap<>();括号里面可以不可以传值,你说可以,那么请问,这个值怎么计算出来的呢,hashmap的数组长度定位多大,才能保证够用且不会造成浪费呢,那么利用上条公式,你就可以逆向的推出HashMap的容量为:Threshold/loadFactor+1(整除不用+1),如下:
在HashMap存13个元素,已知元素的大小了,问:HashMap初始容量应该为多少?
按照上述的计算方式,你会算出:13/0.75+1 = 18,但是,官方要求我们的初始容量的值为2的n次幂,但是我们输入的初始容量的值只有18,不是2的n次幂,该怎么办呢?虚拟机就会根据你输入的值,找到一个大于离18最近的2的n次幂的值,为32
这篇文章对于解析HashMap只能是作为引子,将一些问题抛出来,后面将会慢慢深入源码,深挖HashMap.