老生常谈之HashMap原理
HashMap是Map中最为常见的一个接口,也是每逢面试必问的一个问题。对于Java求职者来说是非常重要的。网上一些关于HashMap的面试文章,小编看过之后并不是非常满意。原因有两:1.劈里啪啦写了一大堆,先不说自己理不理解记不记得住,读者已经懵了?2.文章有些技术点理解不是很准确。我们应该怎么巧妙的回答这个问题,打动面试官。那么接下来和小编再撸一次HashMap,以下仅为参考。
面试官:你平常有了解HashMap吗?说一下HashMap的底层原理?
小 生:当然了解。首先JDK1.7和JDK1.8是有区别的。JDK1.7的时候用的是数组+单链表的数据结构。JDK1.8的时候是数组+链表+红黑树的数据结构,当链表长度超过8(阈值)并且数据总量达到64,就会自动扩容把链表转化为红黑树。时间复杂度从O(n)转化为O(logN),大大的提高了效率。(如果不是特别了解别给自己挖坑,点到为止即可,给自己留点余地,也别给面试官感觉你在背书)。
面试官:那么为什么把链表转化为红黑树的阈值是8,不是6或者是其他的呢?
小 生:这个只能从底层源码说起,通过源码我们知道,如果大于等于6是链表,大于等于8转为树。我们可以通过泊松分布计算结果得出,当桶中结点为8时,出现的概率是最低的,因此常见的情况是桶中个数小于8的情况,此时的性能是和红黑树差不多的,所以没有必要转化成红黑树。(回答到这步面试官一般不会再追问下去,因为他未必懂)
以下是源码作者给出的解释:
* Because TreeNodes are about twice the size of regular nodes, we
* use them only when bins contain enough nodes to warrant use
* (see TREEIFY_THRESHOLD). And when they become too small (due to
* removal or resizing) they are converted back to plain bins. In
* usages with well-distributed user hashCodes, tree bins are
* rarely used. Ideally, under random hashCodes, the frequency of
* nodes in bins follows a Poisson distribution
* (http://en.wikipedia.org/wiki/Poisson_distribution) with a
* parameter of about 0.5 on average for the default resizing
* threshold of 0.75, although with a large variance because of
* resizing granularity. Ignoring variance, the expected
* occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
* factorial(k)). The first values are:
*
* 0: 0.60653066
* 1: 0.30326533
* 2: 0.07581633
* 3: 0.01263606
* 4: 0.00157952
* 5: 0.00015795
* 6: 0.00001316
* 7: 0.00000094
* 8: 0.00000006
* more: less than 1 in ten million
*
泊松分布:是一种统计与概率学里常见到的离散概率分布(有兴趣的同学可以上网了解)。
桶:hashmap的table数组
面试官:那你说说HashMap的put方法过程?
小 生:嗯.....我想下。大概过程如下:
1.调用哈希函数获取key所对应的hash值,计算数组下标。
2.如果没有冲突,直接放入数组,如果出现冲突,则以链表的方式放在链表后面。
3.如果链表长度超过阈值,转为红黑树,如果链表长度低于6,红黑树转为链表。
4.如果结点的key存在,替换其value。
5.如果集合键值对大于12,调用resize方法进行数组扩容操作。
面试官:好,你刚刚说了,数组扩容,那么数组是怎么扩容的?
小 生:(WC,这是要逼死我的节奏,还好留了一手)。这时候会创建一个新的数组,其容量为旧数组的两倍,然后重新计算旧数组中结点的存储位置。结点在新数组的位置有两种情况,原来下标位置或者原来下标加上旧数组的大小。