HashMap

一、HashMap

1.1 JDK 7

底层数据结构:数组 + 链表

调用构造器初始化:初始化的时候会创建一个长度为 16 的数组

存储过程:通过 hashCode 计算出该元素在数组中的存储位置,如果该位置没有元素,就直接添加(如果 map 中的元素个数大于临界值,就扩容);如果有元素,先和该元素比较 hashCode 是否相同,如果相同,改变原位置的 value;如果不同,就调用 equals 方法,如果 equals 返回 true,就改变原位置的 value,如果返回 false,就比较该位置后面的链表,重复上述方法,直到把该桶中的链表全部比较完,如果一路都没有遇到相同的,就把该元素放到链表头部(头插法)

1.2 JDK 8

底层数据结构:数组 + 链表 + 红黑树

调用构造器初始化:初始化的时候没有创建长度为 16 的数组

存储过程:与 JDK 7 的区别在于在首次添加的时候会创建一个底层为 16 的数组;在链表中添加元素时,首先判断该链表是不是红黑树,如果是红黑树,就调用红黑树的添加方法;如果不是,就和 jdk 7 中的比较过程一样,但最后是把元素添加到了链表尾部(尾插法);如果某个桶中的数据个数大于 8 且当前数组的长度大于 64时,该桶就改用红黑树来存储;如果没有大于 64,就实施扩容(2倍)。

注意:扩容之后还得重新计算元素的位置,这个过程非常消耗性能

1.3 HashMap 源码中的重要常量

数组长度

​ HashMap 的数组长度默认为 16,有两方面的考虑:

  • index = hashcode & (table.length-1),16 是 2 的次幂,使用 (2 的次幂 - 1)与 hashCode 做位与操作计算 index 可以全部利用 hashCode 的最后几位,使得只要 hashcode 本身均匀的,hash 算法的结果就是均匀的。
  • 长度为 16 而不是其他 2 的次幂是碰撞几率和空间利用率的综合考虑。

负载因子

  • 负载因子决定了 HashMap 的数据密度;
  • 负载因子越大,数据密度越大,发生碰撞的几率就越高,数组中的链表就容易变长,造成查询或插入时的比较次数增多,性能下降;
  • 负载因子越小,越容易触发扩容,数据密度也越小 (有些位置可能永远用不到) ,发生碰撞的几率越小,查询和插入的效率越高;但是扩容会导致浪费一定的空间,也会导致频繁地重新计算元素的位置,也会影响性能;
  • 根据研究经验,会将负载因子设置为 0.7—0.75,这是碰撞几率和空间利用率的综合考虑。

红黑树的转换和还原阈值

​ 链表转换成红黑树的阈值为 8,红黑树还原回链表的阈值为 6。

​ 在负载因子为 0.75 的情况下,根据泊松分布,链表长度为 8 的概率为百万分之一,基本不太可能发生,所以使用 7 作为临界,大于 7 转成红黑树,小于 7 转回链表。

​ 转成红黑树还有一个条件就是数组长度大于等于 64,因为小于 64 时发生碰撞的概率较大,所以首先考虑的应该时扩容,而不是转成红黑树。

1.4 JDK 7 中的头插法在多线程扩容的时候容易成环

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        //e为空时循环结束
        while(null != e) {
            Entry<K,V> 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
        }
    }
}

​ 两个线程 t1,t2 同时进行扩容,

​ 线程 t1 执行到 1 时被挂起了,此时 e 为 A,next 为 B
在这里插入图片描述

​ 线程 t2 开始从头执行,完成了 2,3,4,因为是头插法,所以最后结果就是 B.next = A,A.next = null
在这里插入图片描述

​ 线程 t1 继续执行,执行到 2 的时候,把 A 插入,是 A.next = null,下一轮 e = B,next = e.next = A,插入完后就是 B.next = A, A.next = null,此时因为线程 t2 导致 B.next 指向了 A,所有 next 又不为空,在第二步时 A.next = B,这就成环了。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值