面试知识点总结之集合框架TreeSet/TreeMap/ConcurrentHashMap/...

TreeSet:

首先 set是可以去重的,tree是实现排序的,总结:

TreeSet是一个包含有序的且没有重复元素的集合,通过TreeMap实现

它继承了AbstractSet抽象类,实现了NavigableSet<E>,Cloneable,Serializable接口。
TreeSet是基于TreeMap实现的,TreeSet的元素支持2种排序方式:自然排序或者根据提供的Comparator进行排序。
 
TreeMap:
TreeMap可以比较元素大小,会对传入的key进行大小排序。自然顺序,自定义的比较器来进行排序;
数据结构:红黑树
TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。 
 
ConcurrentHashMap:
 JDK1.7 和 JDK1.8 的实现方式不同
这里介绍JDK1.8
Node数组+链表+红黑树结构;在锁的实现上,抛弃了原有的 Segment 分段锁,采用CAS + synchronized实现更加细粒度的锁。
将锁的级别控制在了更细粒度的哈希桶数组元素级别,也就是说只需要锁住这个链表头节点(红黑树的根节点),就不会影响其他的哈希桶数组元素的读写,大大提高了并发度。
JDK1.8 中为什么使用内置锁 synchronized替换 可重入锁 ReentrantLock?
  • 在 JDK1.6 中,对 synchronized 锁的实现引入了大量的优化,并且 synchronized 有多种锁状态,会从无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁一步步转换。
  • 减少内存开销 。假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承 AQS 来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。

如何put

  1. 根据 key 计算出 hash 值;
  2. 判断是否需要进行初始化;
  3. 定位到 Node,拿到首节点 f,判断首节点 f:
    • 如果为 null ,则通过 CAS 的方式尝试添加;
    • 如果为 f.hash = MOVED = -1 ,说明其他线程在扩容,参与一起扩容;
    • 如果都不满足 ,synchronized 锁住 f 节点,判断是链表还是红黑树,遍历插入;

 

  4. 当在链表长度达到 8 的时候,数组扩容或者将链表转换为红黑树。

get

  1. 根据 key 计算出 hash 值,判断数组是否为空;
  2. 如果是首节点,就直接返回;
  3. 如果是红黑树结构,就从红黑树里面查询;
  4. 如果是链表结构,循环遍历判断。

get 方法不需要加锁。因为 Node 的元素 value 和指针 next 是用 volatile 修饰的,在多线程环境下线程A修改节点的 value 或者新增节点的时候是对线程B可见的。

 

ConcurrentHashMap 不支持 key 或者 value 为 null 的原因?

 

我们先来说value 为什么不能为 null。因为 ConcurrentHashMap 是用于多线程的 ,如果ConcurrentHashMap.get(key)得到了 null ,这就无法判断,是映射的value是 null ,还是没有找到对应的key而为 null ,就有了二义性。

而用于单线程状态的 HashMap 却可以用containsKey(key) 去判断到底是否包含了这个 null 。

--原文链接:https://zhuanlan.zhihu.com/p/346803874

 
 
 
 
posted @ 2021-03-12 19:19  白玉神驹  阅读(89)  评论(0编辑  收藏  举报