HashMap和ConcurrentHashMap底层原理

1:HashMap

1.1:基本原理

HashMap是以key,value的存储的容器,它是通过计算key的hash值来进行存储的,在查询的时候也是通过key的hash值进行查询,所有查询效率还是不错的,HashMap不支持key重复,value允许重复,key允许一个为null,底层存储使用到了节点对象数组-单向链表-红黑树存储,红黑数是JDK8加入的主要的是用来优化HashMap的查询,HashMap线程也不是安全的,在高并发容易造成数据混乱

1.2数据结构

  1. 数组: 数组存储在内存区间空间是连续的,占用空间比较严重,故而空间复杂度很大,我们在往数组里面插入元素or删除元素都需要在内存中移动元素的位置,来进行修改(复制,移动),这样操作元素就很耗时间,数组来是线性表,线性表的一个特性就是元素都是以一根线的形式在内存中存储,我们查询只需要根据元素索引的位置进行查询就行了,查询时间复杂度为O(1),数组特点容易查询,插入删除慢

  2. 链表:链表在内存中存储数据离散,占用空间比较宽松,往链表结构存储数据,会在内存中生产两块空间,一块存储数据,一块记录数据存储的nxet(指针),所以新增or删除只需更改元素上的指针,查询的话就需要一个一个元素往下找下去,数据复杂度为O(n),故而查询慢,新增和删

  3. hash表: 存储通过计算键值对key的hash值来进行存储的,key就像数组的索引,所以查询非常块,时间复杂度为O(1),hash占用内存也是比较少的

1.3:java7之前实现
在这里插入图片描述
HashMap在java7是以数组加链表实现的,数组里面是一个单向链表,链表每一个元素都是Entry对象,Entry对象有4个字段,key,value,hash值,链表的next指针

1.4:java8实现
在这里插入图片描述
HashMap在Java8添加了红黑树存储,存储变成数组-单向链表-红黑树
HashMap在java7的时候,查询需要根据计算key的hash值来定位数组的具体下标,但是之后,需要顺着链表一个一个比较才能找到我们需要的数据,所以在链表这一步时间复杂度变成O(n),n主要取决于链表的长度,为了降低这部分开销,在Java8中当链表元素超过8个之后,会将链表转换成红黑树存储,为这些位置元素查询时间降低为O(logN)

put方法源码

	// 参数onlyIfAbsent表示是否替换原值
	// 参数evict我们可以忽略它,它主要用来区别通过put添加还是创建时初始化数据的
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 空表,需要初始化
    if ((tab = table) == null || (n = tab.length) == 0)
        // resize()不仅用来调整大小,还用来进行初始化配置
        n = (tab = resize()).length;
    // (n - 1) & hash这种方式也熟悉了吧?都在分析ArrayDeque中有体现
    //这里就是看下在hash位置有没有元素,实际位置是hash % (length-1)
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 将元素直接插进去
        tab[i] = newNode(hash, key, value, null);
    else {
        //这时就需要链表或红黑树了
        // e是用来查看是不是待插入的元素已经有了,有就替换
        Node<K,V> e; K k;
        // p是存储在当前位置的元素
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p; //要插入的元素就是p,这说明目的是修改值
        // p是一个树节点
        else if (p instanceof TreeNode)
            // 把节点添加到树中
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 这时候就是链表结构了,要把待插入元素挂在链尾
            for (int binCount = 0; ; ++binCount) {
                //向后循环
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // 链表比较长,需要树化,
                    // 由于初始即为p.next,所以当插入第8个元素才会树化
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 找到了对应元素,就可以停止了
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                // 继续向后
                p = e;
            }
        }
        // e就是被替换出来的元素,这时候就是修改元素值
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            // 默认为空实现,允许我们修改完成后做一些操作
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // size太大,达到了capacity的0.75,需要扩容
    if (++size > threshold)
        resize();
    // 默认也是空实现,允许我们插入完成后做一些操作
    afterNodeInsertion(evict);
    return null;
}

1.5:扩容机制

  1. capacity: 当前数组容量,始终保持2^n(2的n次幂次方)
    ,可以扩容,扩容后数组大小为当前的2倍
  2. loadFactor: 负载因子,默认0.75
  3. threshold: 扩容阈值,等于capacity*loadFactor

2:ConcurrentHashMap

1:基本原理
ConcurrentHashMap基本原理和HashMap差不多,存储也是通过数组,单向链表,红黑树存储,但是相较于HashMap线程不安全,ConcurrentHashMap是线程安全的,且支持并发,线程安全主要是用通过synchronized-CAS来实现

CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。

CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。

1.1:java7实现

ConcurrentHashMap存储是通过Segment数组加链表实现的,Segment对象也能称之为一段或者槽,ConcurrentHashMap之所以线程安全,是Segment继承了ReentrantLock来加锁,每次锁住一个Segment对象,这样就保证了并发发下全局的线程安全

并行度:concurrencyLevel 并发数默认16个,也就是说ConcurrentHashMap有16个Segments,所以理论上最多同时支持16个线程并发写,前提他们的操作分别分布在不同的segemt上,默认并发数在初始化后不能扩容,但是在还未初始化的时候设置其他值,
在这里插入图片描述
1.2:java8实现
java8对ConcurrentHashMap有较大改动,其中最大的是把Segment数组移除了,换成了node数组存储,保证线安全程换成了JVM级别的synchronized关键字和CAS算法,其次也加入了红黑树,变成node数组+单向链表+红黑树存储,非常类似HashMap
在这里插入图片描述

ConcurrentHashMap put方法描述文章

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值