剖析面试最常见问题之 Java 集合框架

一、Java 集合概览

从下图可以看出,在 Java 中除了以 Map 结尾的类之外, 其他类都实现了 Collection 接⼝。
并且,以 Map 结尾的类都实现了 Map 接⼝。

  • List (对付顺序的好帮⼿): 存储的元素是有序的、可重复的。
  • Set (注重独⼀⽆⼆的性质): 存储的元素是⽆序的、不可重复的。
  • Map (⽤ Key 来搜索的专家): 使⽤键值对(kye-value)存储,类似于数学上的函数 y=f(x),“x”代表 key, "y"代表 value, Key 是⽆序的、不可重复的, value 是⽆序的、可重复的,每个键最多映射到⼀个值。

二、集合框架底层数据结构总结

Java集合框架的底层数据结构主要包括以下几种:

  1. List

    • ArrayList:基于数组实现。它允许通过索引随机访问元素,但在添加或删除元素时可能需要移动其他元素以保持元素的连续性。
    • LinkedList:基于双向链表实现(在JDK 1.6之前为循环链表,JDK 1.7取消了循环)。它支持快速的插入和删除操作,但在随机访问元素时需要遍历整个链表。
    • Vector:与ArrayList类似,也是基于数组实现,但它是线程安全的。
  2. Set

    • HashSet:基于哈希表实现。它使用数组和链表结合的方式来存储元素,并通过哈希函数来计算元素的存储位置。HashSet中的元素是无序且唯一的。
    • LinkedHashSet:继承自HashSet,其内部是通过LinkedHashMap来实现的。它保留了插入的顺序,使得元素在遍历时按照插入的顺序出现。
    • TreeSet:基于红黑树(一种自平衡的排序二叉树)实现。TreeSet中的元素是自然排序或自定义排序的,且唯一。
  3. Map

    • HashMap:基于哈希表实现。它使用哈希函数将键映射到桶中,并在桶中使用链表或红黑树来存储具有相同哈希值的键值对。HashMap中的键是无序且唯一的,但值可以重复。
    • LinkedHashMap:继承自HashMap,它保留了插入的顺序,使得键值对在遍历时按照插入的顺序出现。
    • TreeMap:基于红黑树实现。TreeMap中的键是自然排序或自定义排序的,且唯一。

 三、如何选用集合?

主要根据集合的特点来选⽤,⽐如我们需要根据键值获取到元素值时就选⽤ Map 接⼝下的集合,需要排序时选择 TreeMap ,不需要排序时就选择 HashMap ,需要保证线程安全就选⽤
ConcurrentHashMap 。当我们只需要存放元素值时,就选择实现 Collection 接⼝的集合,需要保证元素唯⼀时选择实现Set 接⼝的集合⽐如 TreeSet 或 HashSet ,不需要就选择实现 List 接⼝的⽐如 ArrayList或 LinkedList ,然后再根据实现这些接⼝的集合的特点来选⽤。

四、为什么要使用集合?

当我们需要保存⼀组类型相同的数据的时候,我们应该是⽤⼀个容器来保存,这个容器就是数组,但是,使⽤数组存储对象具有⼀定的弊端, 因为我们在实际开发中,存储的数据的类型是多种多样的,于是,就出现了“集合”,集合同样也是⽤来存储多个数据的。
数组的缺点是⼀旦声明之后,⻓度就不可变了;同时,声明数组时的数据类型也决定了该数组存储的数据的类型;⽽且,数组存储的数据是有序的、可重复的,特点单⼀。 但是集合提⾼了数据存储的灵活性, Java 集合不仅可以⽤来存储不同类型不同数量的对象,还可以保存具有映射关系的数据 。

五、有哪些集合是线程不安全的?怎么解决呢?

在Java中,许多集合类都不是线程安全的,这意味着在多线程环境中直接使用它们可能会导致数据不一致或其他并发问题。以下是一些常见的线程不安全的集合类及其解决方法:

线程不安全的集合类

  1. ArrayList:ArrayList是线程不安全的,如果多个线程同时修改它,可能会导致数据不一致。
  2. LinkedList:与ArrayList一样,LinkedList也是线程不安全的。
  3. HashSet:HashSet同样不是线程安全的。
  4. HashMap:HashMap是Java集合框架中最常用的类之一,但它也是线程不安全的。

解决方法

  1. 使用同步包装器
    对于ArrayList和HashMap等线程不安全的集合类,Java提供了同步包装器,如Collections.synchronizedList()Collections.synchronizedMap()。这些包装器将集合的每个方法都包装在synchronized块中,从而确保在任意时刻只有一个线程可以访问集合。但是,这种方法的性能可能较差,因为多个线程在访问集合的不同部分时仍然需要等待。

  2. 使用并发集合
    Java并发包(java.util.concurrent)提供了一些线程安全的集合类,如CopyOnWriteArrayListConcurrentHashMapConcurrentLinkedQueue等。这些集合类使用了更复杂的算法和数据结构来确保在多线程环境下的线程安全性。其中,CopyOnWriteArrayListCopyOnWriteArraySet在修改时复制底层数组,从而实现了读操作无锁,写操作线程安全;而ConcurrentHashMap则通过分段锁等技术实现了高效的并发读写。

  3. 使用锁
    在更复杂的场景中,你可能需要更精细地控制对集合的访问。这时,你可以使用Java的内置锁(如synchronized关键字)或显式锁(如ReentrantLock)来保护对集合的访问。但是,这种方法需要谨慎使用,以避免死锁和其他并发问题。

  4. 避免共享可变状态
    如果可能的话,尽量避免在多个线程之间共享可变状态。你可以使用不可变对象(immutable objects)或将状态封装在线程安全的对象中。这种方法可以大大降低并发编程的复杂性。

总之,在选择集合类时,你应该根据你的具体需求和场景来决定是否需要使用线程安全的集合类。如果你需要在多线程环境中使用集合类,那么你应该选择适当的解决方法来确保线程安全性。


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值