Java集合框架

Collection

List

Set

Map

HashMap

hashMap实现原理:数组+链表

  • 如何寻找bucket
    1.使用扰动函数hash()根据key的hashCode计算出一个值hash=> 扰动是为了减少碰撞;
    2.hash&(n-1) => n为2的幂时,hash%n等价于hash&(n-1);

  • 为什么重写了equals方法必须重写hashCode方法?
    详见Java基础部分Java基础

  • 1.8中Node节点保存的数据
    key、val、hash、next

  • 1.8中改为红黑树的阈值为什么是8?
    本质上是为了进行时间与空间的平衡 =>

    TreeNode占用的空间比Node大;
    红黑树的查找、插入、删除时间效率更高;

    当冲突的元素过少时,引入红黑树并未节约多少时间,反而造成了空间浪费
    为什么恰好是8
    之所以是8,是因为Java的源码贡献者在进行大量实验发现,hash碰撞发生8次的概率已经降低到了0.00000006,几乎为不可能事件,如果真的碰撞发生了8次,那么这个时候说明由于元素本身和hash函数的原因,此次操作的hash碰撞的可能性非常大了,后序可能还会继续发生hash碰撞。所以,这个时候,就应该将链表转换为红黑树了,也就是为什么链表转红黑树的阈值是8。 最后,红黑树转链表的阈值为6,主要是因为,如果也将该阈值设置于8,那么当hash碰撞在8时,会反生链表和红黑树的不停相互激荡转换,白白浪费资源。
    详见:详解:HashMap红黑树的阈值为什么是8?

  • HashMap线程安全问题
    1.7使用头插法存在线程安全问题
    若多个线程并发resize(),可能造成死循环

    void transfer(Entry[] newTable)
    {
        Entry[] src = table;					// 旧的数组
        int newCapacity = newTable.length;		// newTable是扩容后的数组
        // 从OldTable里摘一个元素出来,然后放到NewTable中
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];	// 旧数组中每个bucket的第一个结点
            if (e != null) {
                src[j] = null;		// 释放旧数组中对该结点的引用
                do {
                    Entry<K,V> next = e.next;	// 下一个元素
                    int i = indexFor(e.hash, newCapacity);  // 计算e在新数组中的bucket位置
                    e.next = newTable[i];	// 头插法,因此e放在newTable[i]的前面
                    newTable[i] = e;        // 更新newTable[i]
                    e = next;
                } while (e != null);
            }
        }
    } 
    

    最终造成循环链表,从而导致出现cpu100%
    详情参考:JAVA HashMap的死循环
    这里不太好理解,不过关键之处在于:头插法会导致原bucket中的元素在新bucket中形成逆序,这为死循环埋下了隐患
    1.8使用尾插法
    JDK1.8使用尾插法,即使并发情况下也不存在死循环

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值