HashMap底层原理

本文详细探讨了HashMap的数据结构,包括在JDK1.8之前的数组加链表设计以及如何利用“拉链法”解决哈希碰撞。JDK1.8引入红黑树以提高查找效率,当链表长度达到8时转换为红黑树。还介绍了HashMap的优化,如使用寻址算法提高性能,并讨论了扩容过程中可能导致的安全问题,特别是多线程环境下可能导致的死锁。此外,文章还涉及了HashMap的put操作流程及扩容机制。
摘要由CSDN通过智能技术生成

首先来说一说HashMap的结构,在jdk1.8之前是数组加链表,为什么是数组加链表,因为这样可以有效解决hash碰撞问题,java中就使用“拉链法”来解决hash碰撞问题。

一.hash碰撞:简单来说就是

1.HashMap中的Key和对key做一个hashcode()的计算后得到的它在bucket数组中的位置相同时,注意,准确的说是hash计算后得到数组中的位置,(当然,表达成hashcode值相同时也对,但是根本是得到在数组中的位置)也就是数组下标相同时,产生hash碰撞,这个时候就会替换原来的值。(这也就是为什么HashMap的Key是唯一的)

2.那如果只是hash后的值相同,而key不同的话,就会用"拉链法"将要存的放在后续链表中。

好,那么如果只是使用数组加链表的形式,如果产生hash碰撞将元素链到链表中时,也不能保证完全分布均匀,如果一个链表长度过长,那对应的查找元素的时间就会增加,所以jdk1.8之后就加入了红黑树,当链表长度达到阈值8的时候就会将链表转换成红黑树,查找的时间复杂度就会降低。如果说链表中有n个元素,链表的遍历时间复杂度就是O(n),红黑树查找的时间复杂度就是O(logn)。那么查找的时间复杂度就会降低。

二.jdk1.8中对hash算法和寻址算法的优化

之前在对key进行hash(key)计算时,采用的是取模运算,而优化后采用的是寻址算法,即:(n-1) & hash 『n为数组长度』,为什么要使用寻址算法呢?首先 「hash & (n-1)」 效果跟 hash 对 n 取模的效果是一样的, 但是『&』与运算的性能要优于 hash 对 n 取模。

三.那么HashMap中是怎么进行put操作的呢?

首先hash(key),得到hash值在通过『&』与运算((与Key.hashCode的高16位做异或运算)),得到了数组存储位置bucket。

散列表为空,就初始化散列表。

没有发生碰撞就直接添加到相应位置。

发生碰撞:

        1:若key地址相同或者equals后内容相同,则替换旧值

        2:如果是红黑树结构,就调用树的插入方法

        3:链表结构,循环遍历直到链表中某个节点为空,尾插法进行插入,插入之后判断链表个数是否到达变成红黑树的阙值8;也可以遍历到有节点与插入元素的哈希值和内容相同,进行覆盖。

如果桶满了大于阀值,则resize进行扩容

.hashmap扩容

当数组满之后会进行扩容,扩容为原来的两倍。

扩容后还要重新对每个hash值进行寻址,也就是用每个hash值跟新的数组长度 n-1 进行『&』与运算操作。

补充:扩容之后的与运算可能会导致之前的发生hash冲突的元素不再发生冲突。

HashMap 在多线程是不安全的,为啥呢?或为什么说HashMap可能会导致死循环?

这个过程就是发生在扩容阶段,在jdk1.7之前,hashmap在扩容2倍,得到新的数组,会将原来的元素重新hash放入到新的扩容后的数组里面去,因为采用的是头插法,头插法就是总是把新增结点插在头部,会造成链表翻转形成闭环,也就是形成死循环。所以会导致不安全

jdk1.8之后虽然是尾插法,但是两个线程或多个线程同时插入元素,如果元素插入同一个位置,有可能导致覆盖。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值