HashMap超详细介绍

概述

HashMap是我们非常常用的数据结构,由数组和链表组合构成的数据结构。

大概如下,数组里面每个地方都存了Key-Value这样的实例,在Java7叫Entry在Java8中叫Node。
在这里插入图片描述
因为他本身所有的位置都为null,在put插入的时候会根据key的hash去计算一个index值。

哈希冲突

当遇到哈希冲突,可以使用链地址法:

在这里插入图片描述
每一个节点都会保存自身的hash、key、value、以及下个节点,我看看Node的源码。
在这里插入图片描述

哈希冲突时节点如何插入链表

java8之前是头插法,就是说新来的值会取代原有的值,原有的值就顺推到链表中去,因为写这个代码的作者认为后来的值被查找的可能性更大一点,为了提升查找的效率。

但是,在java8之后,都是所用尾部插入了。

为什么?我们首先看一下HashMap的扩容机制:

  • 扩容机制
    数组容量是有限的,数据多次插入的,到达一定的数量就会进行扩容,也就是resize。
    他取决于两个因素:
    HashMap当前长度
    负载因子:默认是0.75
    怎么理解呢,就比如当前的容量大小为100,当你存进第76个的时候,判断发现需要进行resize了,那就进行扩容,但是HashMap的扩容也不是简单的扩大点容量这么简单的。
    分为两步
    1.扩容:创建一个新的Entry空数组,长度是原数组的2倍。
    2.ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。
    需要再散列是因为Hash的公式—> index = HashCode(Key) & (Length - 1),是和数组长度有关系的,长度变为2倍,自然规则也就变了。
  • 回到问题为什么不用头插法了
    现在我们要在容量为2的容器里面用不同线程插入A,B,C,假如我们在resize之前打个短点,那意味着数据都插入了但是还没resize那扩容前可能是这样的。
    在这里插入图片描述
    因为再散列到新数组对于哈希冲突也是采用头插法解决的,在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。

在这里插入图片描述
就有可能导致链表环状。

但是JDK1.8之后采用的尾插法虽然不会导致环状,但是通过源码看到put/get方法都没有加同步锁,多线程情况最容易出现的就是:无法保证上一秒put的值,下一秒get的时候还是原值,所以线程安全还是无法保证。

HashMap默认容量

我记得我在看源码的时候初始化大小是16

你那知道为啥是16么?

在JDK1.8的 236 行有1<<4就是16,为啥用位运算呢?直接写16不好么?
这样是为了位运算的方便,位与运算比算数计算的效率高了很多,之所以选择16,是为了将Key映射到index的算法。

我前面说了所有的key我们都会拿到他的hash,但是我们怎么尽可能的得到一个均匀分布的hash呢?

是的我们通过Key的HashCode值去做位运算。

之所以用位与运算效果与取模一样,性能也提高了不少!

那为啥用16不用别的呢?

因为在使用不是2的幂的数字的时候,Length-1的值是所有二进制位全为1,这种情况下,index的结果等同于HashCode后几位的值。

只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。

这是为了实现均匀分布

为啥重写equals方法的时候需要重写hashCode方法呢?

因为在java中,所有的对象都是继承于Object类。Object类中有两个方法equals、hashCode,这两个方法都是用来比较两个对象是否相等的。

HashMap是通过key的hashCode去寻找index的,那index一样就形成链表了,那怎么找到链表中具体某一个对象呢?

equals!是的,所以如果我们对equals方法进行了重写,建议一定要对hashCode方法重写,以保证相同的对象返回相同的hash值,不同的对象返回不同的hash值。

不然一个链表的对象,你哪里知道你要找的是哪个,到时候发现hashCode都一样,这不是完犊子嘛。

与红黑树的关系

HashMap是有数组链表结构组成的,那么每一个链表就称为桶。
在JDK1.8中,桶满时就会从链表变成红黑树,并且,我们的TreeSet、TreeMap底层都是红黑树来实现的。

  • 特征
    1.红黑树是二叉搜索树。
    2.根节点是黑色。
    3.每个叶子节点都是黑色的空节点(NIL节点)。
    4.每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
    5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点(每一条树链上的黑色节点数量(称之为“黑高”)必须相等)。

红黑树的搜索、插入、删除时间复杂度都是O(logn)。

猜你喜欢

转载自blog.csdn.net/AIJXB/article/details/115124311