Map 常用实现类对比

map 常用实现类比较

允许 key null允许 value null线程安全特性使用场景
HashMap默认初始容量16,加载因子0.75,扩容为旧容量乘2,查找元素快,无序数组+链表的结构JDK1.8后加入了链表转换为红黑树的机制使用哈希表,Entry数组实现键值对存储,无序,快速,可以允许一个null键和多个null,不考虑线程安全\单线程
HashTable默认初始容量11,加载因子0.75,扩容为旧容量2n+1,数组+链表的结构利用红黑树实现,使用了synchronized,所以效率相比较低键值对存储,无序,不允许一个null键和多个null考虑线程安全,优先考虑ConcurrentHashMap (多线程)
TreeMap利用红黑树实现,有序的集合,实现了SortMap接口,存储在TreeMap中的键值按键排序key对象必须实现Comparable接口,所以key不允许为null适用一些有序、排序无重复的场景需要快速增删改查的场景
WeakHashMap键值对会GC垃圾回收,通过弱键WeakRe ference和ReferenceQueue实现,适用于一一些缓存场景缓存、LRU
ConcurrentHashMapConcurrentHashMap由一个个继承ReentrantLock的Segment组成分段锁,相对HashTable的synchronized效率会更高-样安全Java8引入了红黑树键值对存储,无序,多线程考虑线程安全
LinkedHashMap基于 hash 表和链表用于增强 HashMap,此外还维护了双向链表构建一个空间占用敏感的资源池,希望可 以自动将最不常被访问的对象释放掉

HashMap
  • 容量一定是 2 的幂次方
    // 初始化 hashmap 带容量大小的构造方法
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

	// 通过该方法保证容量大小一定为 2 的幂次方
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
  • 底层数据结构

    • jdk 1.7 及之前 HashMap 底层是数组和链表结合在一起使用也就是 链表散列。HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突
    • 相比于之前的版本, jdk 1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间
  • 多线程操作

    • 并发下的 rehash 会造成元素之间会形成一个循环链表;jdk 1.8 后解决了这个问题,但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在其他问题比如数据丢失;并发环境下推荐使用 ConcurrentHashMap

ConcurrentHashMap
  • 底层数据结构
    • jdk1.7 的 ConcurrentHashMap 底层采用 分段数组+链表 实现,jdk1.8 采用的数据结构跟 HashMap1.8 的结构一样,数组+链表/红黑二叉树
    • 保证线程安全的方式
      • jdk1.7及之前: ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率(图片来源)
        图片来源:https://www.cnblogs.com/chengxiao/p/6842045.html

      • jdk1.8及之后:摒弃 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作

        • 计算 hash 值
        • 如果没有初始化就先调用 initTable 方法进行初始化过程(CAS)
        • 如果没有 hash 冲突就直接 CAS 插入
        • 如果还在进行扩容操作就先进行扩容
        • 如果存在 hash 冲突,则加锁来保证线程安全(synchronized)
        • 如果链表数量大于等于 8,且数组长度大于 64,则转为红黑树
        • 如果成功添加就调用 addCount 方法统计 size,并检查是否需要扩容
          请添加图片描述
      • key value 不能为空
        • 源码:

          • if (key == null || value == null) throw new NullPointerException();
        • 二义性:

          • 参考地址:https://www.jb51.net/article/204543.htm
          • 假设 HashMap 的 value 为空,这时候可以通过 containsKey 判断该 key 的 value 为空,还是根本没有 put 过
          • 假设 ConcurrentHashMap 为空,这时候通过 containsKey 判断该 key 的 value 是否为空,此时另一个线程 put 的 value 为空,这时就无法判断是 key 没有 put 过,还是 value 为 null
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

tytler

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

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

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

打赏作者

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

抵扣说明:

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

余额充值