Java集合(一):集合框架与 List 体系
Java集合(一):集合框架与 List 体系
导语:集合框架是 Java 面试的必考重镇。本篇先理清整体继承结构,再看 List 体系的选型、扩容机制与并发替代方案,共 11 题。
一、集合框架概览
1. Java 集合框架的整体结构是怎样的?
答: 两大根接口:
Collection:存储单列元素,子接口有List、Set、Queue。Map:存储键值对(双列),不是Collection的子接口。
常用实现类:
List:ArrayList、LinkedList、Vector、StackSet:HashSet、LinkedHashSet、TreeSetMap:HashMap、LinkedHashMap、TreeMap、Hashtable、ConcurrentHashMapQueue:PriorityQueue、ArrayDeque、BlockingQueue系列
2. List、Set、Map 三者的区别?是否都继承自 Collection?
答:
List:有序、可重复、可存多个null,有索引,支持按下标访问。Set:无序(除LinkedHashSet/TreeSet)、不可重复、最多一个null。Map:键值对,key 不可重复(最多一个null),value 可重复(可多个null)。- 继承关系:
List/Set继承自Collection;Map不继承Collection。
3. 什么场景选用 List / Set / Map?
答:
- 频繁按索引访问、需保序、允许重复 →
List(增删多在尾部用ArrayList,频繁中间增删用LinkedList)。 - 需去重 →
Set(HashSet最快去重、LinkedHashSet保插入序、TreeSet保排序)。 - 需键值映射 →
Map(无需排序用HashMap,需排序用TreeMap,保插入序用LinkedHashMap)。
4. Collection 和 Collections 有什么区别?
答: Collection 是集合的根接口;Collections 是工具类(位于 java.util),提供大量静态方法:排序 sort、二分查找 binarySearch、线程安全包装 synchronizedList/Map、不可变包装 unmodifiableXXX、反转 reverse 等。
二、List 体系
5. ArrayList 和 LinkedList 的区别?
答:
| 维度 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态 数组 Object[] | 双向 链表 |
| 随机访问 | O(1),快 | O(n),慢 |
| 头/尾插删 | 需移动元素,慢(O(n)) | 仅改指针,O(1) |
| 中间插删 | 需移动后续元素,O(n) | 需先遍历定位到插入点,O(n) |
| 尾部插删 | amortized O(1) | O(1) |
| 内存占用 | 连续空间,可能有预留容量 | 每节点额外存前后指针,更占内存 |
| 线程安全 | 均不安全 | 均不安全 |
经验:读多写少(尤其随机访问)用
ArrayList;频繁在头部/中间插入删除用LinkedList。
6. ArrayList 和 Vector 的区别?Vector 为何被淘汰?
答:
- 线程安全:
Vector方法用synchronized修饰,线程安全;ArrayList不安全。 - 性能:
Vector同步开销大,单线程下慢于ArrayList。 - 扩容:
ArrayList默认扩容 1.5 倍(旧容量 + 旧容量/2);Vector默认扩容 2 倍。 - 淘汰原因:
Vector粗粒度锁性能差,需要线程安全时用Collections.synchronizedList或CopyOnWriteArrayList/ConcurrentHashMap等更优方案。
7. ArrayList 的扩容机制是怎样的?
答: 默认初始容量 10(JDK 7 延迟到首次 add 才建 10 容量数组)。当元素数量超过 容量 × 负载因子 时触发扩容:
- 计算新容量 = 旧容量 + (旧容量 >> 1)(即 1.5 倍);
- 通过
Arrays.copyOf把旧数组复制到新数组(代价较高的数组拷贝)。
建议:预知数据量时用
new ArrayList(int initialCapacity)指定初始容量,避免多次扩容拷贝。
8. 多线程场景下如何使用 ArrayList?
答: ArrayList 本身不安全,多线程写入会出现数据覆盖、越界等问题。替代方案:
Collections.synchronizedList(new ArrayList<>()):方法级synchronized,读也加锁,并发一般;CopyOnWriteArrayList:写时复制,读无锁、线程安全,适合读多写极少的场景;- 或用并发容器
ConcurrentLinkedQueue等按业务替代。
9. 为什么 ArrayList 的 elementData 用 transient 修饰?
答: ArrayList 实现了 Serializable,但内部数组 elementData 常有预留但未使用的空间(容量 > 实际.size)。若直接序列化整个数组会浪费空间。因此 elementData 标 transient 不被默认序列化,而 ArrayList 自定义了 writeObject/readObject,只序列化 size 范围内的有效元素,反序列化时再重建,节省存储与传输。
10. Array 和 ArrayList 的区别?如何实现二者互转?
答:
- 数组长度固定、可存基本类型与对象;
ArrayList长度动态、只能存对象(基本类型自动装箱)。 - 互转:
// List -> Array
String[] arr = list.toArray(new String[0]);
// Array -> List
List<String> list = Arrays.asList(arr); // 注意:返回的 List 大小固定,不能 add/remove
// 如需可变:new ArrayList<>(Arrays.asList(arr))11. Stack 了解吗?为什么推荐用 Deque 代替?
答: Stack 继承自 Vector,用 synchronized 实现线程安全(且继承自 Vector 暴露了所有向量方法,破坏栈语义)。现代推荐用 Deque 接口的实现 ArrayDeque 作为栈(push/pop/peek),非线程安全且更高效;需要并发栈用 ConcurrentLinkedDeque 或 BlockingDeque。
