JAVA集合框架中的常用集合及其特点、适用场景、实现原理简介

本文详细比较了ArrayList、LinkedList、HashMap、Hashtable、ConcurrentHashMap、LinkedHashMap、TreeMap和ConcurrentSkipListMap等Java集合框架,探讨了它们在实现原理、线程安全、性能和内存占用等方面的特点。
摘要由CSDN通过智能技术生成

ArrayList基于数组来实现集合的功能,其内部维护了一个可变长的对象数组,集合内所有对象存储于这个数组中,并实现该数组长度的动态伸缩

ArrayList使用数组拷贝来实现指定位置的插入和删除:

插入:

图片.png

删除:

图片.png

LinkedList


LinkedList基于链表来实现集合的功能,其实现了静态类Node,集合中的每个对象都由一个Node保存,每个Node都拥有到自己的前一个和后一个Node的引用

LinkedList追加元素的过程示例:

图片.png

ArrayList vs LinkedList

  • ArrayList的随机访问更高,基于数组实现的ArrayList可直接定位到目标对象,而LinkedList需要从头Node或尾Node开始向后/向前遍历若干次才能定位到目标对象
  • LinkedList在头/尾节点执行插入/删除操作的效率比ArrayList要高
  • 由于ArrayList每次扩容的容量是当前的1.5倍,所以LinkedList所占的内存空间要更小一些
  • 二者的遍历效率接近,但需要注意,遍历LinkedList时应用iterator方式,不要用get(int)方式,否则效率会很低

Vector


Vector和ArrayList很像,都是基于数组实现的集合,它和ArrayList的主要区别在于

  • Vector是线程安全的,而ArrayList不是

  • 由于Vector中的方法基本都是synchronized的,其性能低于ArrayList

  • Vector可以定义数组长度扩容的因子,ArrayList不能

CopyOnWriteArrayList


与 Vector一样,CopyOnWriteArrayList也可以认为是ArrayList的线程安全版,不同之处在于 CopyOnWriteArrayList在写操作时会先复制出一个副本,在新副本上执行写操作,然后再修改引用。这种机制让 CopyOnWriteArrayList可以对读操作不加锁,这就使CopyOnWriteArrayList的读效率远高于Vector。 CopyOnWriteArrayList的理念比较类似读写分离,适合读多写少的多线程场景。但要注意,CopyOnWriteArrayList只能保证数据的最终一致性,并不能保证数据的实时一致性,如果一个写操作正在进行中且并未完成,此时的读操作无法保证能读到这个写操作的结果。

Vector vs CopyOnWriteArrayList

  • 二者均是线程安全的、基于数组实现的List
  • Vector是【绝对】线程安全的,CopyOnWriteArrayList只能保证读线程会读到【已完成】的写结果,但无法像Vector一样实现读操作的【等待写操作完成后再读最新值】的能力
  • CopyOnWriteArrayList读性能远高于Vector,并发线程越多优势越明显
  • CopyOnWriteArrayList占用更多的内存空间

Map类集合

======

图片.png

Map将key和value封装至一个叫做Entry的对象中,Map中存储的元素实际是Entry。只有在keySet()和values()方法被调用时,Map才会将keySet和values对象实例化。

每一个Map根据其自身特点,都有不同的Entry实现,以对应Map的内部类形式出现。

前文已经对Map接口的基本特点进行过描述,我们直接来看一下Map接口的常用实现类

HashMap


HashMap将Entry对象存储在一个数组中,并通过哈希表来实现对Entry的快速访问:

图片.png

由每个Entry中的key的哈希值决定该Entry在数组中的位置。以这种特性能够实现通过key快速查找到Entry,从而获得该key对应的value。在不发生哈希冲突的前提下,查找的时间复杂度是O(1)。

如果两个不同的key计算出的index是一样的,就会发生两个不同的key都对应到数组中同一个位置的情况,也就是所谓的哈希冲突。HashMap处理哈 希冲突的方法是拉链法,也就是说数组中每个位置保存的实际是一个Entry链表,链表中每个Entry都拥有指向链表中后一个Entry的引用。在发生哈希冲突时,将冲突的Entry追加至链表的头部。当HashMap在寻址时发现某个key对应的数组index上有多个Entry,便会遍历该位置上的 Entry链表,直到找到目标的Entry。

图片.png

HashMap的Entry类:

static class Entry<K,V> implements Map.Entry<K,V> {

final K key;

V value;

Entry<K,V> next;

int hash;

}

HashMap由于其快速寻址的特点,可以说是最经常被使用的Map实现类

Hashtable


Hashtable 可以说是HashMap的前身(Hashtable自JDK1.0就存在,而HashMap乃至整个Map接口都是JDK1.2引入的新特性),其实现思 路与HashMap几乎完全一样,都是通过数组存储Entry,以key的哈希值计算Entry在数组中的index,用拉链法解决哈希冲突。二者最大的不同在于,Hashtable是线程安全的,其提供的方法几乎都是同步的。

ConcurrentHashMap


ConcurrentHashMap是HashMap的线程安全版(自JDK1.5引入),提供比Hashtable更高效的并发性能。

图片.png

Hashtable 在进行读写操作时会锁住整个Entry数组,这就导致数据越多性能越差。而ConcurrentHashMap使用分离锁的思路解决并发性能,其将 Entry数组拆分至16个Segment中,以哈希算法决定Entry应该存储在哪个Segment。这样就可以实现在写操作时只对一个Segment 加锁,大幅提升了并发写的性能。

在进行读操作时,ConcurrentHashMap在绝大部分情况下都不需要加锁,其Entry中的value是volatile的,这保证了value被修改时的线程可见性,无需加锁便能实现线程安全的读操作。

ConcurrentHashMap的HashEntry类:

static final class HashEntry<K,V> {

final int hash;

final K key;

volatile V value;

volatile HashEntry<K,V> next;

}

但是鱼与熊掌不可兼得,ConcurrentHashMap的高性能是有代价的(否则Hashtable就没有存在价值了),那就是它不能保证读操作的绝对 一致性。ConcurrentHashMap保证读操作能获取到已存在Entry的value的最新值,同时也能保证读操作可获取到已完成的写操作的内容,但如果写操作是在创建一个新的Entry,那么在写操作没有完成时,读操作是有可能获取不到这个Entry的。

HashMap vs Hashtable vs ConcurrentHashMap

  • 三者在数据存储层面的机制原理基本一致
  • HashMap不是线程安全的,多线程环境下除了不能保证数据一致性之外,还有可能在rehash阶段引发Entry链表成环,导致死循环
  • Hashtable是线程安全的,能保证绝对的数据一致性,但性能是问题,并发线程越多,性能越差
  • ConcurrentHashMap 也是线程安全的,使用分离锁和volatile等方法极大地提升了读写性能,同时也能保证在绝大部分情况下的数据一致性。但其不能保证绝对的数据一致性, 在一个线程向Map中加入Entry的操作没有完全完成之前,其他线程有可能读不到新加入的Entry

LinkedHashMap


LinkedHashMap与HashMap非常类似,唯一的不同在于前者的Entry在HashMap.Entry的基础上增加了到前一个插入和后一个插入的Entry的引用,以实现能够按Entry的插入顺序进行遍历。

图片.png

TreeMap


TreeMap是基于红黑树实现的Map结构,其Entry类拥有到左/右叶子节点和父节点的引用,同时还记录了自己的颜色:

static final class Entry<K,V> implements Map.Entry<K,V> {

K key;

V value;

Entry<K,V> left = null;

Entry<K,V> right = null;

Entry<K,V> parent;

boolean color = BLACK;

}

红黑树实际是一种算法复杂但高效的平衡二叉树,具备二叉树的基本性质,即任何节点的值大于其左叶子节点,小于其右叶子节点,利用这种特性,TreeMap能够实现Entry的排序和快速查找。

关于红黑树的具体介绍,可以参考这篇文章,非常详细:http://blog.csdn.net/chenssy/article/details/26668941

TreeMap的Entry是有序的,所以提供了一系列方便的功能,比如获取以升序或降序排列的KeySet(EntrySet)、获取在指定key(Entry)之前/之后的key(Entry)等等。适合需要对key进行有序操作的场景。

ConcurrentSkipListMap


ConcurrentSkipListMap同样能够提供有序的Entry排列,但其实现原理与TreeMap不同,是基于跳表(SkipList)的:

图片.png

如上图所示,ConcurrentSkipListMap由一个多级链表实现,底层链上拥有所有元素,逐级上升的过程中每个链的元素数递减。在查找时从顶层链出发,按先右后下的优先级进行查找,从而实现快速寻址。

static class Index<K,V> {

final Node<K,V> node;

final Index<K,V> down;//下引用

volatile Index<K,V> right;//右引用

}

与TreeMap不同,ConcurrentSkipListMap在进行插入、删除等操作时,只需要修改影响到的节点的右引用,而右引用又是volatile的,所以ConcurrentSkipListMap是线程安全的。但ConcurrentSkipListMap与ConcurrentHashMap一样,不能保证数据的绝对一致性,在某些情况下有可能无法读到正在被插入的数据。

TreeMap vs ConcurrentSkipListMap

  • 二者都能够提供有序的Entry集合
  • 二者的性能相近,查找时间复杂度都是O(logN)
  • ConcurrentSkipListMap会占用更多的内存空间
  • ConcurrentSkipListMap是线程安全的,TreeMap不是

Set类集合

======

Set 接口继承Collection,用于存储不含重复元素的集合。几乎所有的Set实现都是基于同类型Map的,简单地说,Set是阉割版的Map。每一个Set内都有一个同类型的Map实例(CopyOnWriteArraySet除外,它内置的是CopyOnWriteArrayList实例),Set把元素作为key存储在自己的Map实例中,value则是一个空的Object。Set的常用实现也包括 HashSet、TreeSet、ConcurrentSkipListSet等,原理和对应的Map实现完全一致,此处不再赘述。

图片.png

Queue/Deque类集合

==============

图片.png

Queue和Deque接口继承Collection接口,实现FIFO(先进先出)的集合。二者的区别在于,Queue只能在队尾入队,队头出队,而Deque接口则在队头和队尾都可以执行出/入队操作

Queue接口常用方法:

  • add(E)/offer(E):入队,即向队尾追加元素,二者的区别在于如果队列是有界的,add方法在队列已满的情况下会抛出IllegalStateException,而offer方法只会返回false

  • remove()/poll():出队,即从队头移除1个元素,二者的区别在于如果队列是空的,remove方法会抛出NoSuchElementException,而poll只会返回null

  • element()/peek():查看队头元素,二者的区别在于如果队列是空的,element方法会抛出NoSuchElementException,而peek只会返回null

Deque接口常用方法:

  • addFirst(E) / addLast(E) / offerFirst(E) / offerLast(E)
    自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

复习的面试资料

这些面试全部出自大厂面试真题和面试合集当中,小编已经为大家整理完毕(PDF版)

  • 第一部分:Java基础-中级-高级

image

  • 第二部分:开源框架(SSM:Spring+SpringMVC+MyBatis)

image

  • 第三部分:性能调优(JVM+MySQL+Tomcat)

image

  • 第四部分:分布式(限流:ZK+Nginx;缓存:Redis+MongoDB+Memcached;通讯:MQ+kafka)

image

  • 第五部分:微服务(SpringBoot+SpringCloud+Dubbo)

image

  • 第六部分:其他:并发编程+设计模式+数据结构与算法+网络

image

进阶学习笔记pdf

  • Java架构进阶之架构筑基篇(Java基础+并发编程+JVM+MySQL+Tomcat+网络+数据结构与算法

image

  • Java架构进阶之开源框架篇(设计模式+Spring+SpringMVC+MyBatis

image

image

image

  • Java架构进阶之分布式架构篇 (限流(ZK/Nginx)+缓存(Redis/MongoDB/Memcached)+通讯(MQ/kafka)

image

image

image

  • Java架构进阶之微服务架构篇(RPC+SpringBoot+SpringCloud+Dubbo+K8s)

image

image

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
存中…(img-XI96bzGz-1713178106476)]

  • Java架构进阶之分布式架构篇 (限流(ZK/Nginx)+缓存(Redis/MongoDB/Memcached)+通讯(MQ/kafka)

[外链图片转存中…(img-JEkbAWb4-1713178106476)]

[外链图片转存中…(img-sXyWOi2z-1713178106476)]

[外链图片转存中…(img-o91pEGhp-1713178106476)]

  • Java架构进阶之微服务架构篇(RPC+SpringBoot+SpringCloud+Dubbo+K8s)

[外链图片转存中…(img-p4z3hOrS-1713178106476)]

[外链图片转存中…(img-3CsuKhPs-1713178106477)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 30
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值