JDK7,8 HashMap,ConcurrentHashMap源码分析,区别对比

HashMap源码分析

JDK7

数组+链表

HashMap<String,String> hashMap= new HashMap<>();//数组+链表
//每一个 都是存的Entry对象
hashmap.put("123","1")//key-->key.hashcode()--> hashcode%数组的长度-->index,
//hash冲突,形成链表,头插法,链表向下移动一格到数组中,方便后续从头开始遍历寻找
//头插法更快。尾插法需要遍历到最后,效率低。
//entry 源码
static class Entry<K,V> implements Map.Entry<K,V>{
    final K key;
    V value;
    Entry<K,V> next;
    int hash;
}
put(key,value){

int hashcode=key.hashcode();

int index=hashcode%table.length;

table[index]=new Entry(key,value,null);//第三个参数是next指针。
//第二次插入 发生碰撞   table[index]=new Entry(key,value,table[index]);
}

在这里插入图片描述

翻倍扩容

在这里插入图片描述
在这里插入图片描述

JDK8

1.桶的树形化 treeifyBin()

(1)根据哈希表容量以及元素个数确定是扩容还是树形化;

(2)如果是树形化则遍历桶中的元素,创建相同个数的树形节点,复制内容,建立起联系;

(3)让桶第一个元素指向新建的树头结点,替换桶的链表内容为树形内容;

2.put()

①.判断键值对数组table[i]是否为空,否则执行resize()扩容;

②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;

③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④;

④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;

⑤.遍历table[i],判断链表长度是否不小于7,不小于7就把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;

⑥.插入成功后,判断实际存在的键值对数量是否超过阈值,如果超过了就进行扩容;

区别

  1. 最重要的一点是底层结构不一样,1.7是数组+链表,1.8则是数组+链表+红黑树结构;

  2. jdk1.7中当哈希表为空时,会先调用inflateTable()初始化一个数组;而1.8则是直接调用resize()扩容;

  3. 插入键值对的put方法的区别,1.8中会将节点插入到链表尾部,而1.7中是采用头插;

  4. jdk1.7中的hash函数对哈希值的计算直接使用key的hashCode值,而1.8中则是采用key的hashCode异或上key的hashCode进行无符号右移16位的结果,避免了只靠低位数据来计算哈希时导致的冲突,计算结果由高低位结合决定,使元素分布更均匀;

  5. 扩容时1.8会保持原链表的顺序,而1.7会颠倒链表的顺序;而且1.8是在元素插入后检测是否需要扩容,1.7则是在元素插入前;

  6. jdk1.8是扩容时通过hash&cap==0将链表分散,无需改变hash值,而1.7是通过更新hashSeed来修改hash值达到分散的目的;

  7. 扩容策略:1.7中是只要不小于阈值就直接扩容2倍;而1.8的扩容策略会更优化,当数组容量未达到64时,以2倍进行扩容,超过64之后若桶中元素个数不小于7就将链表转换为红黑树,但如果红黑树中的元素个数小于6就会还原为链表,当红黑树中元素不小于32的时候才会再次扩容。

为什么JDK改用尾插法?

JDK1.7中扩容时,每个元素的rehash之后,都会插入到新数组对应索引的链表头,所以这就导致原链表顺序为A->B->C,扩容之后,rehash之后的链表可能为C->B->A,元素的顺序发生了变化。在并发场景下,扩容时可能会出现循环链表的情况。而JDK1.8从头插入改成尾插入元素的顺序不变,避免出现循环链表的情况。

ConCurrentHashMap源码分析

JDK7

Segement[]数组,Segement[]数组中在存 HashEntry, 一个Segement中对应的entry数量由 初始容量/并发级别算出。

一个Segement最少对应两个Entry.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TPb5fz0o-1626256812421)(C:\Users\10952\AppData\Roaming\Typora\typora-user-images\image-20210714141434486.png)]

当 entry中 链表长度大于 阈值 该Segement中做rehash扩容,与其他Segement无关

Segment的put方法中,会先进行加锁,也就是锁最终加锁的地方在Segment,如果访问的是不同的Segment,就不会有锁的争用,提高了并发性能。

Java7 ConcurrentHashMap基于ReentrantLock实现分段锁

在这里插入图片描述

JDK8

jdk1.8中ConcurrentHashMap取消了分段式锁的封装,存储结构和数据操作原理与普通HashMap一样,数组+[链表|树]。其保证线程安全的核心思想是,插入元素时如果没有冲突,也就是说index处为空,则执行CAS插入操作;如果index处已经有元素了,则使用synchronized锁定index处元素,再进行插入操作。

Java8中 ConcurrentHashMap基于分段锁+CAS保证线程安全,分段锁基于synchronized关键字实现;
在这里插入图片描述

final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        //求hash值
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //初始化
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {				//CAS插入,当位置为空时,不上锁
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                //有冲突才加锁
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodingPeppa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值