前言
分类汇总 10+ 常见的 Java集合 经典后端面试题,并对题目进行了精炼总结,旨在帮助大家高效记忆,在面试中游刃有余,不至于陷入词穷的窘境。
Java 集合
什么是 Java 集合?
集合是存储多个元素的容器,长度可变,存储元素可能是引用类型,可以存储多种类型对象。
请你说一下集合体系,从顶层说起
Java 集合可分为三大类:单列值集合Collection、键值对双列集合Map、工具类。
Collection 接口
-
List 接口:有序集合,允许重复元素。
-
ArrayList:基于动态数组实现。
-
LinkedList:基于双向链表实现。
-
Vector:线程安全的动态数组实现。
-
-
Set 接口:无序集合,不允许重复元素。
-
HashSet:基于哈希表实现。
-
LinkedHashSet:基于哈希表和双向链表实现,保持插入顺序。
-
TreeSet:基于红黑树实现,元素有序。
-
-
Queue 接口:队列,先进先出(FIFO)。
-
LinkedList:也实现 Queue 接口。
-
PriorityQueue:优先队列,基于堆实现。
-
Map 接口:键值对集合,键唯一,值可以重复。
-
HashMap:基于哈希表实现。
-
LinkedHashMap:基于哈希表和双向链表实现,保持插入顺序。
-
TreeMap:基于红黑树实现,键有序。
-
Hashtable:线程安全的哈希表实现。
工具类Collections
-
sort(List< T > list): 对列表进行自然排序。
-
synchronizedList(List< T > list):返回线程安全的列表。
-
reverse(List<?> list):反转列表中的元素顺序。
-
max(Collection<? extends T> coll):根据元素的自然顺序返回集合中的最大元素。
List() 和 Iterate() 的区别?
-
List:是一个集合接口,提供基于索引的访问和操作元素的方法。
-
Iterator:是一个遍历接口,提供统一的方式来遍历集合元素,不依赖于集合的具体实现。
LinkedList和ArrayList有什么区别?
-
LinkedList: 双向链表,查询慢O(n),增删快O(1),内存占用较多,每个元素需额外存储前后引用。线程不安全。适用于频繁插入删除的场景。
-
ArrayList:动态数组,查询快O(1),增删快O(n),内存占用较少,扩容有额外开销。线程不安全。适用于随机访问和尾部操作的场景。
什么是数组越界?
数组越界是指在访问数组元素时,索引超出了数组的合法范围(即小于0或大于等于数组长度)。
简称 HashMap 和 HashSet 的区别?
-
HashMap:基于哈希表的键值对存储,实现Map接口,键唯一,值可重复。内部使用数组和链表(或红黑树)实现。
-
HashSet:基于哈希表的集合存储,实现 Set 接口,存储唯一元素,不允许重复。内部使用HashMap实现,元素作为HashMap的键。
HashSet 如何检查重复?
HashSet 通过计算元素的 hashCode() 确定存储位置,遍历该位置的链表或(红黑树),使用 equals() 方法检查重复元素,确保集合汇总元素唯一。
HashSet 和 TreeSet 的区别?
HashSet:基于哈希表实现,内部使用HashMap。无序。插入、删除和查找操作平均时间复杂度为O(1)。
TreeSet: 基于红黑树实现,内部使用TreeMap。有序,默认按自然顺序或通过比较器 Comparator 排序。插入、删除和查找操作平均时间复杂度为O(n)。
HashMap和HashTable的区别,哪个的性能更好?
-
HashMap: 继承自 AbstractMap 类,实现 Map接口。允许一个键为null,允许多个值为null。使用 Enumeration 进行迭代。默认初始容量为 11,扩容时容量变为原来的 2 倍加 1。非线程安全,没有同步机制,性能更好。
-
HashTable: 继承自 Dictionary 类。不允许键值对为null。使用 Iterator 进行迭代,支持 fail-fast 机制。默认初始容量为 16,扩容时容量变为原来的 2 倍。线程安全,所有方法都是同步的(使用 synchronized 关键字)。
HashMap的底层原理是什么?
HashMap 的底层原理基于哈希表。JDK1.8 前,底层采用数组+链表,使用的是头插法。
JDK1.8后,底层采用数组+链表+红黑树,使用的是尾插法,红黑树优化了链表过长的问题。当链表长度超过阈值(默认为8),链表会转换为红黑树。当红黑树节点数量减少到一定阈值(默认为 6),红黑树会转换回链表。
HashMap 和 ConcurrentHashMap 的区别?
-
HashMap是非线程安全的。ConcurrentHashMap是线程安全的。
-
HashMap是无锁机制。ConcurrentHashMap采用锁分段机制(Java 8 之前)或基于CAS操作(Java 8 及以后),减少锁的粒度,提高并发性能。
-
HashMap允许一个键为null,允许多个值为null。ConcurrentHashMap不允许键或值为空。
-
HashMap:迭代器在检测到结构变化时会抛出 ConcurrentModificationException,是 fail-fast 的。 ConcurrentHashMap 迭代器是弱一致性的,可以在迭代过程中看到其他线程的部分更新,不会抛出 ConcurrentModificationException。
讲一下HaspMap的扩容机制
-
HashMap的默认初始容量为16,默认加载因子为0.75,默认阈值达到12。
-
当 HashMap 中的元素数量超过 容量 * 负载 因子 时,触发扩容。
-
扩容时,将当前数组容量翻倍,即新的容量是原来的两倍。
-
扩容后,并将原有元素重新散列到新的更大的数组中。
-
在JDK8中,为了优化性能,当哈希表的大小小于64时,负载因子为0.5,而不是默认值0.75。
ConcurrentHashMap各版本的变化
- JDK1.5: 引入ConcurrentHashMap,不支持扩容。使用分段锁(Segment)实现。
- JDK1.6:对扩容机制进行了优化。当有线程正在扩容时,其他线程可以继续读取原来的数据结构,从而减少了整个表被锁定的时间。
- JDK1.7:不再使用Segment,而是采用了CAS和synchronized来保证线程安全性。链表结构改为了红黑树结构,增加了对Map.Entry的支持。还添加了一个ConcurrentHashMap.reduceEntries()方法,可以对哈希表中的所有键值对进行并行操作。
- JDK1.8:使用了CAS操作来保证线程安全性,还添加了ConcurrentHashMap.mappingCount()方法,用于返回当前映射表中的键值对数量。