HashMap、HashTable、ConcurrentHashMap你学废了吗

Map

  • Map实现类保存的都是key-value对的

  • key是不可重复的,key的底层是Set集合,Set的一大特性就是具有去重功能

  • value是可重复的,value的底层是Collection集合,是允许重复数据保存的

  • 用来标识map里的每项数据

HashMap、HashTable、ConcurrentHashMap

HashMapHashTableConcurrentHashMap
底层JAVA8前:数组+链表
JAVA8后:数组+链表+红黑树
8前:+分段锁
8后:+CAS+synchronized
数组长度默认16;存储的内容是链表的头节点
通过hash函数计算要存的位置,再遍历链表来获得元素,时间复杂度O(n)
JAVA8之后,引入红黑树,通过常量TREEIFY_THRESHOLO判断是否将链表转化为红黑树来存储,阀值>8转红黑,<6转链表,时间复杂度O(logn)
通过锁细粒度化优化
分段锁:将锁一段一段存储。segments默认长度为16,逻辑上将table数组拆分成多个子数组,每个子数组配置一把锁
table中每个bucket都用一把不同的锁来管理
构造函数仅仅给一些变量赋值,采用Lacy_load的原则,在首次使用时才初始化
函数resize():具备初始化和扩容(2x)两个功能
hash():计算在数组中的位置
安全性不安全
多线程下调用put触发调整大小时可能会存在条件竞争,容易导致死锁
安全
public方法都加入了synchronized修饰符,锁住整个对象
安全:
锁拆的更细,首先使用无锁操作CAS插入头节点,失败就循环重试
若头节点已存在,则尝试获取头节点的同步锁,再进行操作
Collections.synchronizedMap(hashMap),就可以将它包装成一个线程安全的map实例:mutex互斥对象成员,对其中的public方法加入synchronized对mutex进行加锁。串行效率低不允许插入null键
串行效率低
不允许插入null键
sizeCtl标识符
串行效率高

HashMap的put方法

  1. 如果HashMap未被初始化过,则初始化
  2. 对Key求Hash值,然后再计算下标
  3. 如果没有碰撞,直接放入桶中
  4. 如果碰撞了,以链表的方式链接到后面
  5. 如果链表长度超过阀值8,就把链表转成红黑树
  6. 如果链表长度低于6,就把红黑树转回链表
  7. 如果节点已经存在就替换旧值
  8. 如果桶满了(容量16*加载因子0.75),就需要resize(扩容2倍后重排)

HashMap减小碰撞

  1. 扰动函数,使元素位置分布均匀,减小碰撞的几率
  2. 使用final对象作为key,采用合适的equals()和hashCode()方法,因为final的不可变性,能缓存不同键的hashcode,可以提高获取的速度,String、Integer

HashMap的hash

本质:使用 与操作来代替取模:

HashMap的扩容resize

  • 默认负载因子是0.75,当一个map填满了75%的bucket时扩容

  • 扩容:创建两倍的原来大小的数组来重新调整map的大小,并调用rehash将原来的对象放入到原来的bucket数组中

  • 假设现在是4个元素,扩容一倍,变成8个元素,那么之前的元素可能要重新计算它在数组中的位置。

    • 对于1来说,1在4个元素时排序在第1位,它在8个元素时还是排序在第一个。
    • 对于6来说,它在4个元素时排在第2位,在8个元素时就需要排在第6位。6从2迁移到6,移动了4个位置,刚好是扩容一倍的长度。
    • 所以,对于扩容2倍的情况来说,所有的元素,位置要么不变,要么是变化为旧的位置+扩容长度。当然,实际上这里的操作是二进制的位运算,这里只是拿十进制举例。

ConcurrentHashMap特性

  • 大小控制标识符sizeCtl:

    • 含义:-1正在初始化,-n表示有n-1个线程正在进行扩容操作,n或0表示的是初始化或者扩容的大小
    • volatile修饰:多线程之间可见
  • 不允许null key

  • CAS更新需要不断重试,直到成功为止

  • 通过computeIfAbsent、compute可以构建java本地缓存

ConcurrentHashMap的put方法

  1. 判断Node[]数组是否初始化,没有则进行初始化操作

  2. 通过hash定位数组的索引坐标,是否有Node节点,如果没有则使用CAS进行添加(链表的头节点),添加失败则进入下次循环。

  3. 检查到内部正在扩容,就帮助它一块扩容。

  4. 如果f!=null,则使用synchronized锁住f元素(链表/红黑二叉树的头元素)

  5. 如果是Node(链表结构)则执行链表的添加操作。

  6. 如果是TreeNode(树型结构)则执行树添加操作。

  7. 判断链表长度已经达到临界值8,当节点数超过这个值就需要把链表转换为树结构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值