Map综述
概述
- HashTable基于哈希表,是线程安全的,其同步是通过synchronized实现的,核心操作put和get均通过synchronized修饰,所以并发效率低,现在已经不再推荐使用。
- HashMap基于哈希表,是非线程安全的,对并发无要求的话,推荐使用这个map。
- LinkedHashMap继承了HashMap,它改变了HashMap无序的特征,使用双向链表来会维护key-value对的次序,非线程安全。LinkedHashMap支持两种类型的顺序,一种是按照插入顺序(默认),一种是访问顺序。最近最少使用算法(LRU,Least Recently Used)就是基于LinkedHashMap访问顺序模式实现的。
- TreeMap是有序的集合,底层是通过红黑树实现的。
- ConcurrentHashMap基于哈希表,是线程安全的,通过CAS和synchronized实现并发的同步操作。
零星知识点
HashTable
- 其同步是通过synchronized实现的,核心操作put和get均通过synchronized修饰,锁的粒度太粗,所以并发效率低,现在已经不再推荐使用。如果想使用并发的HashMap,使用ConcurrentHashMap。
HashMap
HashMap 1.8相对于1.7做了3点改动
- HashMap通过“链表法”解决哈希冲突,1.7使用的是头插,而1.8则使用的是尾插。1.7头插的方式,在并发扩容的场景下,可能会引起死循环。死循环产生的场景:
- 两个线程同时进入扩容流程,假设旧的hash数组某个链表上包含两个键值对,且扩容后依旧同属一个链表。old[] -> a -> b -> null
- 线程A执行后:new[] -> b -> a -> null。
- 线程B执行,处理键值对a后:new[] -> a -> b -> a 这样就形成了环,迁移和访问都会产生死循环。
- 如果链表的长度超过8,则会将链表转成红黑树,加快查询的速度。
put操作核心流程
- 1.判断hash数组是否为空,如果为空,则初始化hash数组。初始化hash数组,关键是指定几个核心参数:hash数组的大小(初始默认16),hash数组的动态扩容的阈值(0.75*16)。所以hash数组使用的是懒汉模式,使用时才进行初始化。
- 2.hash数组里面存储的元素是Node节点,Node可以是单向链表的头结点,也可以是红黑树的根节点。前面一步之后,hash数组就肯定存在了,根据key的hash值和当前哈希数组长度,计算出hash索引。根据hash索引查找在hash数组中对应的元素,分为下面4种情况:
- 查找得到的元素为空,说明该hash索引对应的链表为空,直接创建一个新的节点,hash索引对应的引用指向这个节点。此时链表的长度为1,且新建的节点便是这个链表的首节点,链表是单向链表,新增的节点放到链表的尾部(超过8将会调整为红黑树)。
- 查找得到的元素不为空,且对应的key等于新加入的key(通过==和equal两种方式进行比较),说明hashMap中已经存在对应的key,记录对应节点,最后根据onlyIfAbsent参数统一处理。
- onlyIfAbsent参数指定了,如果hashMap中已经存在对应key了,是舍弃新值,还是覆盖旧值。onlyIfAbsent意为只有不存在对应key才插入,