一文带你快速全面掌握Java集合框架面试题



Java集合框架

相关接口继承关系和实现

集合类存放在java.util包内,主要有三种:Set、List、Map

  • Collection:该接口是集合List(包括Queue)、Set最基本的接口。
  • Iterator:迭代器,可以通过迭代器遍历集合中的数据
  • Map:是映射表的基础接口


List 接口

List是非常常用的一种数据类型,List是有序的集合。Java List一共存在三个实现类:

List的实现类其元素都是有序的,且支持重复元素。

  1. ArrayList
  2. LinkedList
  3. Vetcor

ArrayList(基于数组实现)

ArrayList是List接口最常用的实现类。内部是通过数组实现的,所以它允许对元素进行随机访问。当向ArrayList的中间位置插入或删除元素时,需要对数组进行复制移动的代价比较高。因此,他适合随机查找和遍历,不适合插入和删除

Vector(基于数组实现,线程同步)

Vector与ArrayList一样都是通过数组实现的,不同的时他支持线程的同步,也就是某个时刻只有一个线程能够向对Vector进行写操作,避免多线程同时写引起的不一致性问题。但是我们说要实现线程的同步需要较高的花费,因此它的访问往往比ArrayList慢

LinkedList(基于链表实现)

LinkedList是采用的链表实现,所以它很适合进行数据的动态删除和插入。但是他不支持随即访问(换句话说就是访问很慢)。另外了它还提供了List接口里里面没有提供的方法,专门用来操作链表头和链表尾的元素,可以当作堆栈、队列和双向队列使用。

三个实现类的比较
ArrayListVectorLinkedList
底层实现数组数组链表
支持线程同步
随机访问效率高-
增删数据效率

Set接口

Set接口的实现类其元素是无序的,也就是说存取顺序可能不同,另外它不支持重复元素的存储。

那是如何实现无法存储重复元素的呢???

这就涉及到了—>什么是对象相等?

我们说对象相等的本质是对象的hashCode值(Java是依据对象的内存地址计算出的此序号)判断的,如果想让两个不同的对象视为相等,就必须要重写Object类的hashCode()方法和equalls()

方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NXvcbZvd-1636637675256)(https://s3.bmp.ovh/imgs/2021/11/e282eac4bde70f60.png)]

HashSet(哈希表)

哈希表边存放的是哈希值。HashSet是无序的。也就是说元素存储的顺序并不是按照存入顺序来的,而是根据其对应的哈希值进行存储的,所以取值呢也是按照哈希值取值的。我们都知道一个对象的哈希值是通过元素的hashCode()方法来获取的。HashSet首先会判断两个值的哈希值,如果哈希值相等,再判断equals方法,如果equal方法结果为true,HashSet则视为相同元素,即无法同时存储。如果不同的话就视为不同的元素。

那要是哈希值相同equals为false,也就是HashSet认为他们不是相同元素,而HashSet它呢又是根据哈希值进行存储,那该如何实现哈希值相同equals为false的元素呢???

在相同的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)也就是同一列中。

下面的图可以清楚的表达:

总的来说:HashSet呢它是根据哈希值来确定元素存放在内存中的位置。一个哈希值位置上可以存放多个元素。

TreeSet(二叉树)
  1. TreeSet是使用二叉树的原理对新增加(add())的对象按照指定的顺序,每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。
  2. Integer和String对象都可以进行默认的TreeSet排序,而自定义类的对象是不可以的,自己定义的普通的类必须实现Comparable接口,并覆盖相应的compareTo()函数,才可以正常使用。
  3. 在覆盖compareTo()函数时,要返回相应的值才能使TreeSet按照一一定的规则来排序。
  4. 比较此对象与指定对象的顺序,如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
LinkedHashSet(HashSet+LinkedHashMap)

​ 对于LinkedHashSet而言,他继承于HashSet,又基于LinkedHashMap来实现。LinkedHashSet底层使用LinkedHashMap来保存所有元素,他继承于HashSet,其所有的方法操作上又与 HashSet 相同,因此 LinkedHashSet 的实现上非常简单,只提供了四个构造方法,并通过传递一个标识参数,调用父类的构造器,底层构造一个 LinkedHashMap 来实现,在相关操作上与父类 HashSet 的操作相同,直接调用父类 HashSet 的法即可。

Map接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4HMCGkbz-1636637675258)(https://s3.bmp.ovh/imgs/2021/11/86351cdf952add7d.png)]

HashMap(数组+链表+红黑树)

HashMap根据键的hashCode的值来存储数据,大多数情况下可以直接定位到他的值,因此具有很快的访问速度,但遍历顺序却是不确定的。HashMap最多只允许一条记录的键为null,但是是允许多条记录为null的。HashMap是非线程安全的,也就是说啊在任意时刻可以有多个线程对HashMap进行写操作,可能会导致数据的不一致性。要想让HashMap满足线程安全,可以使用Collections的synchronizedMap方法来使得HashMap具有线程安全的能力,或者使用CurrentHashMap。下面图展示了HashMap的结构。

Java7中的HashMap实现

从大体方向上来看,HashMap是一个数组,然后数组的每个元素是一个单向列表。上图中,每个绿色的实体是嵌套Entry的实例,Entry吧包含四个属性:key、value、hash值和用于单向链表的next

  1. capacity:当前数组的容量,始终保持2^n,可以进行扩容,扩容后的数组大小为当前的2倍。
  2. loadFactor:负载因子,默认为0.75
  3. threshold:扩容的阈值,等于capacity*loadFactor
Java8中的HashMap实现

Java8上对HashMap进行了一些修改,最大的不同就是利用了红黑树,所以其底层实现是包括数组、链表、红黑树的。

根据Java7版本对于HashMap的介绍,我们已经知道了在查找的时候,我们根据hash值可以很快的定位到数组的具体下表,但是之后的话,需要顺着链表一个个比较下去才能找到我们所需要的,时间复杂度取决与链表,也就是O(n).在Java8中为了降低这部分开销,当链表中的元素超过8个以后,会将链表转换为红黑树,在这些位置上的查找可以降低时间复杂度为O(logN)。相关结构如下图所示:

ConcurrentHashMap
Segment段

ConcurrentHashMap和HashMap的思路是差不多的,但是因为它支持并发操作,所以要相对复杂一些。整个ConcurrentHashMap是由一个Segement组成,Segment代表“部分、成对”的意思。所以很多地方都将其描述为分段锁。

线程安全的实现

简单的来讲,ContcurrentHashMap是一个Segement数组,Segment通过继承ReetrantLock进行加锁,所以每次所住的呢都是一个Segement,这样一想就可以保证每个Segment的线程安全,也就实现了全局的线程安全。

我们可以通过下面这张图进行理解:

并行度(默认是16)

concurrentLevel:并发级别、并发数、Segment数。他的默认值时16,也就是说ConcurrentHashMap有16个Segments。所以理论上这个时候可以同时支持16个线程并发写操作,只要它们的操作分布在不同的Segment上。一旦初始化它是不可以扩容的。再具体到每个Segment内部,其实每个Segment很像之前的HashMap,不过这个要保持线程安全,所以处理起来麻烦一些。

JDK7中的实现
  1. 先将数据分为一段一段进行存储,然后会给每一段数据分配一把锁,当一个线程占用锁来访问其中一个数据时,其他段的数据也能被其他线程访问。
  2. 采用Segment+HashEntry的方式进行实现。结构如下:

一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。

JDK8中的实现
  1. 在jdk1.8中放弃了对Segment臃肿的设计,取而代之的是采用Node+CAS+Synchronized来保证并发安全进行实现。Synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率提升N倍。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xl6rT2BW-1636637675264)(https://s3.bmp.ovh/imgs/2021/11/57628561aa224c88.png)]

ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。HashMap 没有考虑同步,HashTable 考虑了同步的问题。但是 HashTable 在每次同步执行时都要锁住整个结构。ConcurrentHashMap 锁的方式是稍微细粒度的。

HashTable(线程安全)

HashTable是遗留类,很多映射的常用功能和HashMap类似,不同的是它继承自Dictionary类,并且是线程安全的,任意时间只有一个线程能对HashTable进行写操作,但是并发性能不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。HashTable不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap进行替换。

使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

TreeMap

TreeMap实现了SortedMap接口,能够把它保存的记录根据键排序,默认是按照键值的升序排序,也可以指定排序的比较器,当使用Iterator遍历TreeMap时,得到的记录是按照排序的。如果使用排序的映射,建议使用TreeMap。在使用TreeMap时,key必须实现Comparble接口或者在构造TreeMap时传入自定义的Comparator,否则在运行时抛出Java.util.ClassCastException异常

LinkedHashMap

LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值