HashMap原理

HashMap源码

HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,这个数组就是HashMap的主干

在这里插入图片描述

我们都知道数组中添加数据,要根据数组下标进行添加,那我们怎么获取下标呢?

通过Entry中的key获取要添加的数组下标index

1.如果直接使用key进行hash算法,得出的结果很难作为下标

System.out.println("1111111111111111".hashCode()); //-1225568000

怎么解决?

​ 可以将index = HashCode(Key) % Length。但是取余的效率很低在HashMap中没有使用。

​ 在HashMap中使用位运算更高效获取index index = HashCode(Key) & (Length - 1)

i = (n - 1) & hash  //使用与运算更高效,化为二进制进行与运算只有对应位置全为1结果才是1

但是怎么才能确保与运算出来的结果均匀分布数组下标

在HashMap数组的长度通常固定为16或2的幂 为什么?

1.例如:数组长度(length)为 :16

“1”的hash值是49对应的二进制为 0011 0001

数组长度(length)为 :16

(length-1)=15 对应的二进制为0000 1111

1"的hash值   0011 0001
      15         0000 1111
      &          _________      求出index=1 ,因为15的高四位全是0 所以与运算求出的index0~15
                 0000 0001      之间。正好对应数组下标。

2.假如:数组长度(length)为:10

1"的hash值   0011 0001
      9          0000 0101
      &          _________      这时候对应的与运算结果只有 0101,0001 ,0100 不会均匀分布数下
                 0000 0001

但是对于数组长度为16的HashMap不管hash值怎么变都是后四位进行与运算,很大概率出现index相同导致链表过长。这使hash值多出的高位没有任何意义。

所以要想办法让高位参与运算

static final int hash(Object key) {
   
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

扰动函数

为了解决这个问题,在hash方法中又给hashCode添加了一次运算,俗称“扰动函数”。其实本质是把hasCode的也高位拉进来,一起参与运算。hashCode返回的int类型有32位,正好一分为二,划分为高16位和低16位。每次得到hashCode值之后,强行将高16位同低16位先进行一次运算,这样就能够解决hashCode不松散或者等差数列的问题。

这个时候就又出现了另一个问题,为什么要用异或运算而不是其他位运算呢?

  • 如果采用与&运算,那么高位就会被0与掉,运算结果整体就会向0方向偏移;
  • 如果采取异或^运算,运算结果整体就会向另一个方向偏移;

而异或运算,则是“不同为1,相同为0”,具有更多的随机性和分布性。

右移16位异或可以同时保留高16位于低16位的特征

"1314".hashCode()                  1 0111 0000 1011 1100 0101
"1314".hashCode()>>>16 无符号右移                        1 0111
     ^(按位异或相同为0不同为1)        __________________________   
                                   1 0111 0000 1011 1101 0010
    
    得到的hash值                    1 0111 0000 1011 1101 0010
        15                                               1111
         &                         __________________________     
                                                       0010   
                           
这样的运算得到的index散列行更强不至于出现一个下标中链表过长,从而降低hash碰撞的概率。

产生hash冲突

链表解决
在这里插入图片描述

将hash冲突的数据使用链表的形式进行尾插法(1.7使用头插法)

数组扩容

(JDK1.8为例)
如图

先知道三个概念
table:存储HashMap节点信息,是个数组
slot:hash槽,即table[i]
bullet:hash桶,相同hash槽上所有元素的集合

这张图有点像一张二维表。HashMap的数据结构采用是分而治之的思想,按照一定的规则将数据一排排存放,查找的时候先找到数据在哪一排,然后在从这排数据挨个找。提高效率。
那么每一排的数据是怎么存放的。以链表的形式存储的,并且满足一些条件时,链表会转化为红黑树。当然,红黑树也会再变成链表。

HashMap简单来说是底层数据结构是数组+链表+红黑树的组合,
数组记录着每一列的位置,链表和红黑树表示列上元素的排列规则。

在源码中,用table表示数组,代码如下

transient Node<K,V>[] table;

Node是HashMap的内部类,是链表上的节点。里面的元素有hash值,键值对以及指向下一个节点的next指针,代码如下:

static class Node<K,V> implements Map.Entry<K,V>{
   
        final int hash;
        final K key;
        V value;
        Node
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值