总结篇---map类(深入了解不同类的区别)

前言

没什么好写的,这个主要写map的深入学习,其中肯定还有很多问题没有写到,所以这篇文章还需要继续更新,往后可能也会将list类和set类加进去

表格对比

HashMapHashSetLinkedHashMapTreeMapcurrentHashMaphashtable
线程不安全不安全不安全不安全安全安全
值为空允许不允许允许允许不允许不允许
键为空允许不允许允许不允许不允许不允许
顺序无序无序有序有序无序无序

为什么有的键值可以为null,有点智能值为null,有的键值都不能为null呢?

  • TreeMap 键不允许为null

TreeMap 是通过key值进行哈希计算然后进行排序的,所以默认情况下键是不允许为null的

  • 线程安全的Map类键值都不能为null

其实主要是针对二义性,也就是如果你获取到的是一个null你能知道两个结果:

1、不存在 2、存在但是为null

但是在并发的环境下,你就没办法知道你在得到值的过程中是不是有其他的线程将它赋值为null

详解

1、HashMap

  • jdk1.7之前使用的是头插法
  • jdk1.8之后使用的是尾插法,链表长度超过8之后,会变成数组+红黑树
    时间复杂度从O(n)降为O(logn))
  • HashMap的主干是一个Entry数组
  • hashMap的默认初始容量(或者是entry的大小)是16个,其会有一个负载因子,用于当hashMap中的数据量等于容量*负载因子时,hashMap会进行扩容,扩大的容量是原本的2倍。负载因子的默认初始值为0.75
  • HashMap的默认长度为16和规定数组长度为2的幂,是为了降低hash碰撞的几率,实现hash分布均衡

数据结构

  • JDK1.7版本的是ReentrantLock+Segment+HashEntry
  • JDK1.8版本中的是synchronized+CAS+HashEntry+红黑树

JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法,那么他们为什么要这样做呢?

  • 因为JDK1.7是用单链表进行的纵向延伸,当采用头插法就是能够提高插入的效率,但是也会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。

JDK1.8为什么使用内置锁synchronized来代替重入锁ReentrantLock:

  • 低粒度加锁方式,synchronized并不比ReentrantLock差,
    粗粒度加锁中ReentrantLock可能通过Condition来控制各个低粒度的边界,更加的灵活,而在低粒度中,Condition的优势就没有了
  • JVM的开发团队从来都没有放弃synchronized,而且基于JVM的synchronized优化空间更大,使用内嵌的关键字比使用API更加自然
  • 在大量的数据操作下,对于JVM的内存压力,基于API的ReentrantLock会开销更多的内存

2、ConCurrentHashMap

如何保证HashMap线程安全的?

  • 使用ConCurrentHashMap

为什么使用ConCurrentHashMap呢?像我们可以使用hashtable或者使用synchronize或者lock来实现线程安全。

  • 主要是因为ConCurrentHashMap的并发度或者说他的效率更高
  • hashtable是synchronize方法,底层数组+链表实现,无论可以还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低
  • ConCurrentHashMap 底层采用分段的数组+链表实现,线程安全,使用锁分段技术,它将map分段,在不同段上使用锁。首先将数据分成一段一段地存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问

3、LinkedHashMap

  • 键值都允许为空
  • 线程不安全的
  • 有序的(主要特点)
  • 它有插入顺序(按插入时排序)和访问顺序(访问节点后,会将节点移动到最后面)
  • key值重复会被覆盖,value值允许重复
  • 继承至HashMap,其操作方法基本上与HashMap相同,但是他重写了一部分的方法,主要用于维护双向链表

它与Hash的主要区别?

  • 其维持了一个双向链表,用于解决遍历顺序和插入顺序一致的问题
  • LinkedHashMap的Entry元素,除了继承HashMap的Entry元素,又新增了两个元素beforeafter的引用
  • 重写了一部分方法

LinkedHashMap读取和存储过程

  • 存储:LinkedHashMap并未重写父类HashMap的put方法,
    而是重写了父类HashMap的put方法调用的子方法
    void addEntry(int hash, K key, V value, int bucketIndex) 和
    void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的双向链接列表的实现。
  • 读取:LinkedHashMap重写了父类HashMap的get方法
    实际在调用父类getEntry()方法取得查找的元素后,
    再判断当排序模式accessOrder为true时,记录访问顺序,将被访问节点移动到链表的尾部

如何迭代有序?

  • 重新定义了数组中保存的元素Entry(继承于HashMap.node),该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了双向链接列表。仍然保留next属性,所以既可像HashMap一样快速查找

LinkedHashMap的put方法煤油重写,继承至HashMap,那它是如何实现其双向链表的?

  • 重写的newNode方法,添加了两个新元素(before和after)
  • 通过LinkNodeLast方法吧entry添加到链表的尾部,实现双向链表的建立

删除过程

  • 其过程并不复杂,就做了三件事:
    • 根据 hash 定位到桶位置
    • 遍历链表或调用红黑树相关的删除方法
    • 从 LinkedHashMap 维护的双链表中移除要删除的节点

4、TreeMap

  • TreeMap的实现是红黑树算法的实现

TreeMap和LinkedHashMap都是有序的HashMap,那他们的区别

  • TreeMap是按键key排序,默认升序,也可以通过指定方法排序
  • LinkenHashMap是按插入时排序,同时他还有访问排序

参考链接

其中一大部分有参考下面转载链接

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值