HashMap源码1.8实现思路

大家都知道,hashMap的结构是数组加上链表,但是在jdk1.8中,链表的结构是可以随着链表的长度增加而改变成树形结构(红黑树);

先来看看源码中的一些常数:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 默认的数组大小
static final int MAXIMUM_CAPACITY = 1 << 30;//数组的最大值
 static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认的负载因子
static final int TREEIFY_THRESHOLD = 8;//如果链表长度超过了8就会对链表处理,进行变形,变成了树形结构,红黑树,提高查询效率
transient Node<K,V>[] table;//hashmap中数组的表示

先来看看一波运算符号:

<<      :     左移运算符,num << 1,相当于num乘以2    例子:110(6)  11(3)
>>      :     右移运算符,num >> 1,相当于num除以2  

所以1<<4 就是2^4=16 ,在后面代码会见到,这个就是hashMap的默认容量大小,看到这里我们会有一个疑问,为啥不好好写16,而要写成1<<4的写法呢?       首先在计算机的底层中,我们的数据都是使用0和1来表示,而写成这样可以提高效率,不用进行进制之间的转换;

之前我们说了,左移是乘以2,那么为什么数组的长度一定要是2的倍数?  由这个问题我们来看看hashmap的实现思路

先来看看 这样一个过程:  1的二进制为00001 , 15的二进制为01111,16的二进制为 10000    32的二进制为100000

00001&01111=00001  也就是 1与15为1            假设x 可能为0或者1    x00001&100000=x00001   

看懂上面的算式,在继续往下看:

因为首先我们通过key来计算hashcode,得到的hashcode值会出现比hashMap数组的大小更大的情况,我们要把一个结点存入数组当中,最后一定是要一个数组长度范围内的下标,所以我们需要对hashcode进行处理,可以用能多方法,比如(hashcode%数组长度),但是在hashcode中使用了hashcode&数组长度(2^n次方-1,换成2进制都是1111....)的方法,运算更快,根据运算0&1=0,1&1=1,    最后得到的数完全取决于hashcode后的几位数,这样符合hash运算的均匀分布,与一个全1的数字,出现的可能性更多,而如果与(1001),那么一定结果的中间两个数为0,那么得到的结果的可能性就减少了,这样就会增加hash冲突的可能;

 

另外做为上一个问题的延伸

函数resize()的功能是初始化数组的长度和扩容数组的长度(懒加载,put后才后去初始化大小),在扩容数组的时候,数组的容量要左移以为,也就是乘以2,那么扩容了,节点的hashcode是不变的,但是数组的长度变了,我们需要用hashcode&数组的长度来计算出放入数值的下标,这里有一个优化的步骤,首先我们的数组扩容后,数组的长度就是原来的2倍,那么我们使用hashcode去和现在数组的长度去相与,以16为例,原来与1111,而扩容后与11111,那么这个时候节点的新的位置,实际上只会有两种情况,要么在原来的位置不变,要么就是增加了一个旧的数组的长度,因为与上1111和与上11111,结果的后4位是不变的,而最高位完全取决于节点key计算出的hashcode的倒数第5位,如果这个位是为0,那么它的位置不变,如果它是1,那么它就会编程原来的位置+10000(16),所以只要把10000去与它的hashcode,如过是10000,那么就加上16,如果不是就不变;

 

 

总的来说hashmap的流程如下:首先新建一个hashmap,当put()方法之后,使用resize()方法去初始化数组,通过key来得到hashcode,并且得到hashcode之后,去和数值的length-1相与,这样的得到了与需要存入数值的位置

猜你喜欢

转载自blog.csdn.net/qq_39512671/article/details/81048590