HashMap内部结构初探

Posted 拂晓杂谈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashMap内部结构初探相关的知识,希望对你有一定的参考价值。

最近有朋友在后台给我发消息说自己在面试的时候遇到了面试官提问HashMap的内部结构的问题。今天就来简单讲一下hashmap的原理。


首先,先上图:

HashMap中存储的是键值对,也就是Entry<k,v>。

在HashMap中存在着一个Entry<k,v>的数组,数组的默认初始长度是16。如上图左侧竖着的一列所示,每当一个新的键值对需要被存储的时候,那么放到这个数组的哪个位置呢?就需要对执行一次哈希操作,来计算该键值对在Entry<k,v>数组中的存放位置,一般通过对key取哈希,然后对Entry<k,v>的长度进行取模,来决定放入哪个位置,公式:hash(key)%len。


但是这样会带来一个问题,比如我存入的一个键值对A被放到了Entry<k,v>数组的第一个位置(index=0),然后我存键值对B的时候,对B的key取哈希再对长度取模之后,发现得到的结果还是0。这个时候就产生了哈希冲突。

不要着急,键值对B也会被放到第一个位置(index=0),但是它会链接在键值对A的后边,如上图横着所示。Entry<k,v>数组的每一个位置都会横着一个链表,表示这个链表上所有的Entry节点的的key经过 hash(key)%len 运算之后都会得到相同的值。这一个链表被称为一个桶,bucket。


当从hashmap中取一个值得时候,hashmap会先根据传入的key确定要查找的键值对在哪个桶里,之后再在这个桶的链表上查找相应的键值对。


负载因子:

我们知道,Entry<k,v>数组的初始长度是16,也就是说,如果被填满的话,初始的HashMap有16个桶,但是,都被填满显然并不是一个好的结果。所以提出了负载因子,负载因子被设置为0.75,指的是,当HashMap中的桶有超过75%的桶被填入键值对,那么就要对这个HashMap的桶的个数进行扩容,扩充为原来的两倍。重新扩容之后,需要对HashMap中所有的键值对对象重新计算哈希值,重新放入不同的桶中。也就是说,扩容前在一个桶一条链上的两个键值对,扩容后可能就不在同一个桶里了。


空间与时间的权衡:

负载因子默认为0.75,也就意味着有0.25的桶永远是空着的,如果你想节省空间,把负载因子调大,那么哈希碰撞次数就会变多,每个桶里的链表上的长度会变长,查找时间增加。

如果把负载因子调小,那么哈希碰撞次数会变少,每个桶里链表长度减少,查找时间减少,但是浪费的空间会增加。

所以,负载因子设置为0.75是一个空间和时间权衡的结果。


查询优化:

桶+链表的方式中,查找的过程只要是确定在哪个桶O(1)和遍历链表O(n),所以查找的复杂度是O(n)。

在新的JDK1.8中,对此进行了优化,当一个桶中的键值对的个数大于8时,就不使用链表来存,而使用红黑树。使用红黑树之后的查询复杂度是O(logN)。


关于HashMap内部原理的介绍就先简单讲到这里,以后有机会再深入剖析,或者以后遇到具体的问题再来具体讨论。

以上是关于HashMap内部结构初探的主要内容,如果未能解决你的问题,请参考以下文章

聊聊 HashMap 和 TreeMap 的内部结构

Java-数据结构

HashMap的内部实现机制,Hash是怎样实现的,什么时候ReHash

HashMap 内部原理

HashMap 内部原理

HashMap的工作原理(图文+例子)详解,绝对简单通俗易懂