HashMap底层原理,为什么每次都是2次幂,为什么链表达到8才转红黑树......

一、HashMap的结构

1.jdk1.7

HashMap的底层是数组+链表
jdk1.7的这种结构当发生多次hash碰撞的时候,链表的长度会很长,大大的降低了他的效率

2.jdk1.8

HashMap的底层是数组+链表+红黑树
当链表长度大于8的时候,链表就会转为红黑树,提高了他的效率

二、构造函数

HashMap() 
  构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。 
HashMap(int initialCapacity) 
  构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。 
HashMap(int initialCapacity, float loadFactor) 
  构造一个带指定初始容量和加载因子的空 HashMap。 

1.HashMap()

在创建HashMap时会给一个默认的初始容量16,默认的加载因子0.75

2.HashMap(int initialCapacity)

在创建时传一个初始容量(但是他并不会按照初始容量来创建)

3.HashMap(int initialCapacity, float loadFactor)

创建一个具有指定初始容量和加载因子的空HashMap

三、自动扩容和加载因子

HashMap会自动扩容,当元素个数超过容量的0.75时就会进行扩容,每次扩容两倍,也就是说每次它的容量都会是2的次幂

四、为什么传入初始容量,他不会按照初始容量来创建?为什么每次都是扩容二倍,都是2的次幂?


    public HashMap(int initialCapacity) {
    	//可以看出他调用的是public HashMap(int initialCapacity, float loadFactor)构造方法
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

从他的源码中可以看出,他调用的还是 public HashMap(int initialCapacity, float loadFactor)这个构造函数,只是加载因子传的是默认值0.75。
我们分析一下,他把initialCapacity(初始容量)传进去后,先进行了判断,判断初始容量是否小于0,小于0的话就抛异常,如果不小于0继续往下。
判断初始容量是否大于HashMap的最大容量,如果大于,就把最大容量赋值给初始容量
然后再进行判断,判断加载因子,此处我们用的第二个构造,传的加载因子是默认的0.75,他这块判断的是加载因子是否小于等于0,是否是NaN(0.0/0.0) ,如果符合就抛异常
进行了一系列判断之后,开始给变量赋值,加载因子直接赋值,但是初始容量此处调用了一个tableSizeFor()方法,我们来看一下这个方法

 /**
     * Returns a power of two size for the given target capacity.
     */
    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;
    }

我们可以带入具体的数值运算一下,此处带入11
1.先对11进行减一 得10
2.n = n | n >>> 1 进行或运算(只要有1就为1)和右移运算
在这里插入图片描述
3.n |= n >>> 2
在这里插入图片描述
4.n |= n >>> 4
再次进行运算会发现还是15
5. n |= n >>> 8; n |= n >>> 16;
再运算还是15
6. return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
判断n的值如果小于0就把他赋值为1,不小于0就判断是否大于最大容量值,如果大于等于就给n赋值最大容量的值,否则的话就对n进行加1操作

分析可以看出这个方法是返回大于传入参数并且是离参数最近的2的次幂的值
所以他并不会根据传入的初始容量来创建HashMap

原因:因为HashMap在存储元素时会根据元素的HashCode值运算出该元素应该被存在哪个位置,我们可以通过取模运算计算元素的存储位置h=hash%length,但是模运算效率极低,所以采取&运算,h=(length-1)&hash,length代表数组长度,hash代表hashCode值,h为计算出来的位置,当数组长度是2的次幂时,两种方式算出来的下标是一样的,但是&运算效率比%运算要高得多,所以容量总为2的次幂

五、为什么加载因子是0.75?

当加载因子是1时,意味着HashMap集合存满才会进行扩容,虽然提高了对空间的利用,但会提高hash碰撞的次数,同时产生了很多链表,这显然不合理。
当加载因子为0.5时,意味着HashMap存一半就要扩容,这样减少了hash碰撞,提高了查询效率,但是对空间的利用率太大
所以折中取了个0.75

六、为什么链表达到8才变为红黑树

在这里插入图片描述
hashMap的节点分布遵循泊松分布,按照泊松分布的计算公式计算出了链表中元素个数和概率的对照表。
在这里插入图片描述
在这里插入图片描述

泊松分布:描述某段时间内,某件事发生的概率

在这里插入图片描述

公式中的参数:
  • λ:代表事件发生的频率
  • k:代表数量
  • e:自然常数 2.718281828459
    默认调整大小的参数平均为0.5,也就是λ=0.5 ,意思就是0.75的阈值,进行扩容的频率平均是0.5
    解释:当进行扩容时,某个数组下标下存储元素个数(K)的概率
    例如:我的容量是16,当数组存够12个是,数组下面的元素个数的概率
    代入公式
    在这里插入图片描述
    可以算出他对应的概率在这里插入图片描述
    所以当他达到扩容的时候,数组下面有8个元素的概率非常的低,为0.00000006,所以在链表长度大于8的时候再转红黑树,链表转红黑树也是需要时间的,只有那么点概率会产生红黑树。
    另外红黑树的查找效率是log(n)长度为8时查找效率时3,长度为8链表的查询效率是8/2=4,当链表长度为6时,查询效率为6/2=3,链表转红黑树也要时间,所以选择8很合理。

七、转红黑树的条件

当hashMap数组容量小于64时,此时索引下面的链表长度超过8,hashMap不进行红黑树的转换,而是进行扩容,重新计算元素的索引位置,当数组超过64后并且索引下面的链表长度超过8,此时会转红黑树。

 final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        //判断数组是否为空或者数组长度是否小于64
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        //当满足条件后进行扩容操作,每次扩容二倍,然后返回,不进行红黑树的转换
            resize();
            
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }
  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值