单线程下HashMap工作原理

1.HashMap中的关键属性

观察源码,可以看到

(1)负载因子:loadFactory

他的默认值是0.75,表示在扩容前,HashMap空间填满程度的边界

(2)threshold

他是记录HashMap所能够容纳的键值对的边界,计算规则:负载因子 乘以 数组的长度

(3)size

他是用来记录HashMap实际存在的键值对的数量

(4)modCount

他用来记录HashMap内部结构发生变化的次数

(5)DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

常量属性,HashMap默认的容量值,默认是16

2.HashMap存储结构:HashMap是采用:数组 + 链表 + 红黑树(JDK1.8)

HashMap的数组部分称作Hash桶,数组元素保持在一个table的属性中,当链表长度大于8时,链表的数据将会以红黑树的形式进行存储,当链表长度降到6时,以链表形式进行存储。

每个Node节点,保存了用来定位数组索引位置的hash值和key、value,以及链表指向的下一个Node节点 。

Node类是HashMap的内部类,实现Map.Entry接口,他的本质可以理解成一个键值对

3.HashMap的工作原理

当我们向HashMap中插入数据时,首先要确定Node在数据中的位置,如何确定Node的存储位置?

最开始的hashCode: 1111 1111 1111 1111 0100 1100 0000 1010
右移16位的hashCode:0000 0000 0000 0000 1111 1111 1111 1111
异或运算后的hash值: 1111 1111 1111 1111 1011 0011 1111 0101

实际就是 --求模取余法

h = hashCode % (table.length - 1)

用hash值和数组的长度减1,取模,最后得到数组的下标,这样可以保证数据下标不越界

或者——过位运算

h&(length-1)保证获取的index一定在数组范围内,举个例子,默认容量16,length-1=15,h=18,转换成二进制计算为 

        1  0  0  1  0
    &   0  1  1  1  1
    __________________
        0  0  0  1  0    = 2

最终计算出的index=2。有些版本的对于此处的计算会使用 取模运算,也能保证index一定在数组范围内,不过位运算对计算机来说,性能更高一些(HashMap中有大量位运算)

所以最终存储位置的确定流程是这样的:

4.当对象的HashCode相同时

因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。

5.如果两个键的 HashCode 相同,你如何获取值对象?

当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,然后获取值对象。找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。

发散:

问题:Jdk1.7到Jdk1.8HashMap 发⽣了什么变化(底层)?

底层数据结构不同:

  1. 1.7中底层是数组+链表
  2. 1.8中底层是数组+链表+红⿊树,加红⿊树的⽬的是提⾼HashMap插⼊和 查询整体效率

 链表插入位置不同:

  1. 1.7中链表插⼊使⽤的是头插法
  2. 1.8中链表插⼊使⽤的是尾插法,因为1.8中插⼊key和value时需要判断链表元素个数,所以需要遍历链表统计链表元素个数,所以正好就直接使⽤尾插法

 扩容策略不同

  1. 1.7中是只要不小于阈值就直接扩容2倍
  2. 1.8的扩容策略会更优化,键值对数量大于阈值时会扩容,且链表长度大于8时,转变为红黑树前会检测hash桶数量不足64时,也会进行扩容,而且扩容过程中,发现有树节点不足6时,会降为链表

 HashMap在初始化和put后容量和阈值的变化

    private  static void test() throws Exception {
        //默认初始容量来创建一个HashMap
        HashMap m = new HashMap();
        //获取HashMap整个类
        Class<?> mapType = m.getClass();
        //获取指定属性,也可以调用getDeclaredFields()方法获取属性数组
        Field threshold =  mapType.getDeclaredField("threshold");
        //将目标属性设置为可以访问
        threshold.setAccessible(true);
        //获取指定方法,因为HashMap没有容量这个属性,但是capacity方法会返回容量值
        Method capacity = mapType.getDeclaredMethod("capacity");
        //设置目标方法为可访问
        capacity.setAccessible(true);
        //打印刚初始化的HashMap的容量、阈值和元素数量
        System.out.println("初始数据 - 容量:"+capacity.invoke(m)+"    阈值:"+threshold.get(m)+"    元素数量:"+m.size());
        for (int i = 0;i<25;i++){
            m.put(i,i);
            //动态监测HashMap的容量、阈值和元素数量
            System.out.println("容量:"+capacity.invoke(m)+"    阈值:"+threshold.get(m)+"    元素数量:"+m.size());
        }
    }

输出结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值