HashMap简析

本文主要介绍Java中HashMap的相关知识。它采用数组+链表结构以提高效率,阐述了关键属性如初始容量、填充因子等。还讲解了resize扩容原理、hashIndex计算方式,以及put、get、remove等操作的原理,帮助理解HashMap的运行机制。
这两天有空,看了一下HashMap的实现原理,在此记录一下。



如上图为HashMap的结构

1、HashMap是数组+链表的结构,数组,查找效率是O(1),删除和插入是O(n),链表插入、删除是O(1),插入是O(n);使用数组+链表结合的方式,提高效率

2、关键属性

(1)、初始容量(capacity)

(2)、填充因子(loadFactor)

(3)、阈值(threshold)

(4)、UNTREEIFY_THRESHOLD(table[i]位置的所有bin转为链表的条件,值为6)

THREEIFY_THRESHOLD(table[i]位置所有bin转为红黑树的条件,值为8)

(5)、MIN_TREEIFY_CAPACITY(在将链表转为红黑树的时候,判断table.length如果小于MIN_TREEIFY_CAPACITY,需要resize,MIN_TREEIFY_CAPACITY值为64)

(6)、MAXIMUM_CAPACITY( table的最大长度)

其中,threshold = capacity*loadFactor,当map.size>threshold时,需要resize进行扩容;

          table的长度是大于等于capacity的最近的2的幂,这样做的好处是,为了更方便更快的计算hashIndex,即,找寻的key对应的table数组的下标(具体方式在下面讲)

3、resize扩容原理

(1)、如果是第一次的话,并且没有设定初始容量initialCapacity即创建HashMap的时候,没有传参,则table的长度为默认值16,threshold为初始长度16*初始填充因子0.75;

(2)、否则,是第一次,并且传参(传参的时候,根据传入的参数,重新计算threshold为大于等于capacity的最近的2的幂,具体方式为

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}复制代码

从而得到的值为2的幂,详解:

比如n=cap-1为  01xx.......xx

       n>>>1为:001xx..xx,再与n或操作得n=011xx..xx;

       n>>>2为:00011xx..xx,再与n或操作得n=01111xx.xx;

       n>>>4为:000001111xx..xx,再与n或操作得n=011111111xx..xx;

      .......

     以此类推,右移16位,则最多得到32个连续的1,保证从最高位的1到末尾全部为1,然后再加1,得到了最近的大于cap的2的幂

),table的长度为threshold;

(3)、不是第一次,则判断table.length是否已经达到最大限度MAXIMUM_CAPACITY,如果是,threshold设置为Integer.MAX_VALUE,并返回;否则,threshold在原来的基础上*2,即扩容2倍。然后将oldTable的内容复制到newTable上(

          由于在扩容的时候,直接是在原来的基础上*2,即左移一位,因此可以理解为,扩容后的最高位要么为1(扩容2倍,数组长度是原来的2倍),要么为0(没有扩容,即已经达到最大);即高位为0的话,索引就是原索引,是1的话,索引变为原索引+oldCap。具体重新分配逻辑为:


)。

4、hashIndex的计算方式

(1)、计算hash

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

使key的hashCode,右移16位,使key的高16位保持不变,然后再异或,充分利用高位,从而避免浪费,并且降低碰撞的频率(若只利用低16位,会容易碰撞)

(2)、(n - 1) & hash

由于上面说过,数组的长度是2的幂,因此这里只用逻辑与而不是取余,系统开销小

使用上面的方式计算hashIndex,充分利用高位与逻辑运算,降低了碰撞率与执行的效率;同时,对value进行插入的时候,使用链表以及红黑树,也降低了碰撞率复制代码

5、put原理

(1)、利用hashCode找到hashIndex对应的table[hashIndex]位置的值e

(2)、如果e为空,则直接插入

(3)、如果e.hash==hash&&((k=e.key)==key)||(key!=null&&key.equals(k)),则key相同,根据参数onlyIfAbsent,或者e.value为空,判断是否需要覆盖

(4)、否则,如果是一个红黑树,则插入红黑树里面,否则jdk8插入链表尾部,jdk7是头插法(插入链表的时候,会判断table[i]位置上的bin个数是否满足要转为红黑树的条件,如果是,则转为红黑树)

上述4步执行完之后,判断数组中以及插入的bin的个数即map.size是否大于threshold,如果是,需要resize

6、get原理

计算hashIndex,获取table[hashIndex]位置的值,返回

7、remove原理

计算hashIndex,获取table[hashIndex]位置的值,如果为空,就是没找到,否则在链表或者红黑树里面删除


转载于:https://juejin.im/post/5c8e389c51882545ef0c98ab

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值