Hashmap原理

HashMap
数组 单向链表 红黑树 jdk1.8尾插

1: hashmap不是线程安全的
主要体现在resize的时候 每个线程都会生成一个newTable 赋引用给oldTable 但只有一个线程会成功
2:HashMap中key和value都允许为null。key为null的键值对永远都放在以table[0]为头结点的链表中。

为什么hashMap的必须是容量是2的次方?
加快运算,比如resize的时候,重新排布节点的时候,位运算比mod快的多

HashMap和HashTable的区别?
1:HashTable是线程安全的 用synchronized修饰了方法,HashMap线程不安全

2:都可以构造时传入初始容量和负载因子
HashTable默认容量是11 负载因子跟HashMap一样都是0.75
HashMap要求容量必须是2的倍数
HashTable不要求容量是2的倍数所有源码里有很多%效率相对较低 3:HashMap的存储结构是 数组+链表+红黑树
HashTable存储结构是 数组+链表

HashMap和ConcurrentHashMap的区别?
1.ConcurrentHashMap的数据结构与HashMap基本相同,只是在put的过程中如果没有发生冲突,则采用CAS操作进行无锁化更新,只有发生了哈希冲突的时候才锁住在链表上添加新Node或者更新Node的操作。
2.像get一类的操作也是没有同步的。
3.ConcurrentHashMap 不允许存放null值。
4.ConcurrentHashMap 的大小是通过计算出来的,也就是说在超高的并发情况下,size是不精确的。这一点后面有空再补上。
5.ConcurrentHashMap引入分段锁,
6.我们可以使用CocurrentHashMap来代替Hashtable吗?
我们知道Hashtable是synchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线程安全性。它们都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。因为ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。

当前table的容量是通过调用table.length方法得到的,没有用变量去保存

用户的初始容量调整为2^n后,先赋值给了threshold暂存

threshold(阈值):capacity * load factor
在这里插入图片描述
最多能容纳2^30个桶
在这里插入图片描述
桶的默认大小16(DEFAULT_INITIAL_CAPACITY)在这里插入图片描述
负载因子(load_factor):默认0.75
在这里插入图片描述
size是已经put的节点的数量
在这里插入图片描述
存用户传入的负载因子
在这里插入图片描述
TreeNode
在这里插入图片描述
Node
在这里插入图片描述

hash函数

hashCode 是int 类型的 一共是 32位,这里是返回:
hashCode的高16位和高16位异或结果
例子:
hashcode: 0000 0100 1011 0011 1101 1111 1110 0001
h >>> 16 : 0000 0000 0000 0000 0000 0100 1011 0011
answer: 0000 0100 1011 0011 1101 1011 0101 0010在这里插入图片描述
tableSizeFor
返回比用户传入容量大的最小的2的次方 比如传入10 返回16
传入8也是16因为至少是16
在这里插入图片描述

什么时候扩容?

当节点的数量大于threshold的时候,注意是节点的数量,不是桶的占用数量,
比如即使所有节点好巧不巧的都在第一个桶中,第一个桶都升级成树了,其他桶都还没用,这时候也需要扩容!

扩容过程

1:确定新的容量,新的阈值

        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {//如果这句执行了表示绝对不是第一次用来初始化的resize()
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        //如果这里能执行,那么这一次一定是用来初始化的那次resize(),如果oldThr大于零则说明,用户调用的构造方法传入了初始容量
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {  //如果这里能执行,说明这一次一定是用来初始化的那次resize(),并且用户构造时没有传入初始容量,hashmap提供的构造方法里没有只能传入load_factor的构造方法
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        //设置新阈值
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;

2:确定了新容量和新阈值开始扩容
扩容有三种情况:
1:初始化的第一次扩容,直接new就行了,不用考虑重新放置的问题
2:某个桶的位置是链表
3:某个桶的位置是红黑树
节点所在桶的位置只能有两个:
1:j
2 : j+oldCap

        //首先new一个新容量大小的table数组
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;//oldTable = table;table = newTable 指向新数组
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {//当前桶的位置不为空,
                    oldTab[j] = null;
                    if (e.next == null)//如果只有一个元素
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)//如果是红黑树
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // 将链表内的节点分类
                    
                        Node<K,V> loHead = null, loTail = null;//偶数倍数个oldCap链表集合
                        Node<K,V> hiHead = null, hiTail = null;//奇数倍数个oldCap链表集合
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {//如果是偶数倍数个oldCap,则位置不变
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {//如果是奇数倍数个oldCap,则位置+cap
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;//对应位置赋值
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;//对应位置赋值
                        }
                    }
                }
            }
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值