Hello,大家好,我是公子照谏。
今天是《老板,我想进大厂》系列的第一篇文章。在这个系列中,我会分享在前段时间中,我和我的小伙伴在面试中遇到的各种问题。希望我们的经历可以带给大家收获。
那么废话就不多说了,开始这个系列的第一篇文章《HashMap》指北。
本文代码基于:Java 1.8_192,Java 1.7
如果代码有出入,请以作者的版本为准。
HashMap的底层是散列表,简单的说就是数组加链表的组合结构。
HashMap在进行put/get操作时,通过对Entry的Key进行Hash操作来存取数据,如果不同的Key得到了相同的Hash值,则会在该位置上构建链表。
在Java 8之后,对散列表的结构进行了优化,单个链表长度超过8之后,会转变为红黑树。
对链表的查找需要遍历链表,时间复杂度为O(N),而对红黑树的查找,时间复杂度为O(lgN)。
HashMap中数组的长度是16。当元素数量达到了阈值的时候,HashMap会进行扩容。阈值的大小由加载因子和当前容量决定。
需要注意的是,这里是HashMap中存储的元素数量,即便数组只使用了一个节点,其余元素均在这个节点上构建链表,HashMap也会进行扩容,为的是将链表上的元素均匀分布到数组上,加快访问速度。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
扩容可以分为两步:
HashMap的put和get操作都要依赖于对Key进行Hash的值,这个值是确定元素在数组中的下标,通过下面的公式计算:
index = e.hash & (newCap - 1);
当数组的大小发生改变时,index的计算结果也会不同,如果不进行rehash操作,那么已经存入到HashMap中的元素,将无法获取。
Java 8之前是真正意义上的rehash,对key重新进行Hash,而在Java 8之后,HashMap的内部类Node会存储Hash值,只是利用 Hash & length 来确定数组下标。
Java 8之前,如果多个元素定位到数组的同一个位置,则会在链表的头部进行插入,Java 8之后改为尾部插入。先来看下Java 7的源码:
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry e : table) {
while(null != e) {
Entry next = e.next;// 代码1
if (rehash){
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];// 代码2
newTable[i] = e;// 代码3
e = next;// 代码4
}
}
}
假设有如下HashMap,A,B两个线程同时put:
put完成后,线程A抢到CPU资源,进行resize。遍历到3的,且执行到代码1,此时e = 3,next = 2;随后线程B抢到CPU资源,完成resize操作后,HashMap如下:
这时,CPU资源交还给线程A,线程A继续resize操作。
要注意的是,线程B已经修改过2和3的指向,此时的关系是2->3,而线程A拿到的关系是3->2。
此时的链表指向关系已经乱了,那么结果也不会好到哪里去。
线程A执行完代码2~4后,e= 2,进入第二次循环,继续执行代码1,上面已经提到过,线程B修改了2和3的指向关系,所以此时拿到的结果是:e = 2,next = 3。另外,需要注意的是,线程A,已经将3插入到
接着执行到代码2,2指向3,执行代码3,将2赋值到数组上,执行代码4,e = 3。
接着进入第三次循环,此时3的next为null,略过前面的内容,执行到代码2,此时的该位置上的元素是2,那么3指向2,执行代码3,将3赋值到数组上,执行代码4,next为null,跳出循环,此时,我们得到一个结果:
至此,“大功告成”,环形链表形成。
如果是尾插法,那么情况应该是这样的:
依旧是线程A在执行在进入循环后丢失CPU资源,此时e = 1,next = 2,此时线程B完成resize操作。
线程A重新获取CPU资源,执行完第一次循环后,1被放在了下标0,此时e = 2;
进入第2次循环,e = 2, next = 3,执行完代码后,2被放在了数组上,e = 3;
进入第3次循环,3正常放置即可,此时HashMap如下:
原计划是打算一口气写完,我遇到的关于HashMap的全部面试题的,不过写到现在发现已经写了很多了,而且,关于头插法形成环形链表这块,如果不是“人肉逻辑机”的话,还是要自己手动画一下的,这样比较容易理解。我们来回顾下,这期都写了什么:
虽然只写了4个点,但是也已经涵盖了关于HashMap的大部分内容,下期主要讲两个方面:
我会持续不断的更新这个系列的,毕竟我在4~5月经历了20+面试,录音,录屏超过25个小时,收集150+最新各大厂面试题的男人。