hashMap

148 阅读5分钟

关于hashmap的几个核心知识点

1.HashMap原理,内部数据结构

一图胜千言

image.png HashMap底层使用哈希表(数组+链表),当链表过长会将链表转换成红黑树以实现O(logn)时间复杂度内查找.

通过源码验证一下

image.png

image.png

2.HashMap中put方法过程

第一步:对Key求Hash值,然后计算下标

第二步:如果产生碰撞,以链表的方式链接到后面

第三步:如果链表长度超过阈值(TREEIFY_THRESHOLD == 8),就转成红黑树

第四步:如果节点已经存在就替换

第五步:如果桶满了(容量 * 加载因子),就需要进行resize扩容

3.HashMap中hash函数是怎么实现的?还有哪些hash的实现方式

直接看源码

image.png

这行代码的意思是:对key的hashCode值h右移16位再进行异或.举个例子:

假如Key的hashCode 000000001100 000000000011
右移16位000000000000 000000001100
进行异或(相同0,不同1)000000001100  000000001111

可以看到高16bit不变,高16位低16bit做了一个异或,这样做的好处是:

1.异或相比与,或更分散.

2.混合原始哈希码的高位和低位,以此来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。

3.3还有哪些实现方式

开放寻址法

当关键字key的哈希地址p =H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,若p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。

再哈希法

就是当通过某个hash函数计算的key存在冲突时,再用另外一个hash函数对这个key做hash,一直运算直到不再产生冲突。这种方式会增加计算时间,性能影响较大。

公共溢出区

4.讲一下扩容过程

HashMap的扩展原理是HashMap用一个新的数组替换原来的数组。重新计算原数组的所有数据并插入一个新数组,然后指向新数组。如果阵列在容量扩展前已达到最大值,阈值将直接设置为最大整数返回。

扩容的时机:

添加完一个数据后,比较当前size和容量,如果大于就执行resize()

手写hashMap的实现

初始容量是怎么计算方法:

static final int tableSizeFor(int cap) {

    int n = cap - 1;

    n |= n >>> 1;

    n |= n >>> 2;

    n |= n >>> 4;

    n |= n >>> 8;

    n |= n >>> 16;

    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

}

最后得出的结果一定是2的N次幂,

原理就是看最高位在哪里,最后加1变成00010000 

为什么要这样做呢,容量为什么建议是2的幂次方?有两个原因

1.源码中官方解释大概意思,以二次幂展开,容器的元素要么保持原来的索引,要么以二次幂的偏移量出现在新表中. 这样可以尽可能的减少元素位置的移动

image.png

2.可以使元素均匀的散布hashmap中,减少哈希碰撞,因为hashmap确定元素小标是根据(n-1) & hash

image.png

因为n为2的幂次方n-1的二进制数据的低位就全部为1了,比如当数组长度为16,那么15的二进制就为1111,这样不仅效率高在计算数组下标index的时候才能更好地利用h的散列性

put方法的过程

image.png

Put的时候利用 hash值 & (n-1) 保证下标在索引范围内,举个例子

假如hashCode 000000001100  000000001111
&上n-1(&都为1才为1)000000000000 000000001100
结果000000001100  000000001111

相同节点,JDK8之前利用头插法插入到链表中,JDK8之后采用尾插法.为什么这么改呢

JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法,那么他们为什么要这样做呢?因为JDK1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。

但是为什么尾插法会避免死循环? blog.csdn.net/qq_45915957…

Hashmap为什么不是线程安全的

1.put的时候会造成覆盖

2.Resize 的时候,会造成循环链表的问题

红黑树

如果链表长度超过阈值(TREEIFY_THRESHOLD == 8),就转成红黑树,关于红黑树的知识,先粗浅的看一下它长什么样子,这部分内容就放在数据结构与算法那部分章节再分享吧.

image.png

参考链接:

blog.csdn.net/liuxingrong… blog.csdn.net/fengxi_tan/… blog.csdn.net/weixin_4436…