# 20. 为什么 subList 不是独立的 List,而是原 List 的视图?
# 标准答案
subList(int fromIndex, int toIndex)
返回的不是新的 List
对象,而是原 List
的一个视图(View)。这意味着 subList
依赖原 List
,任何修改 subList
都会影响原 List
,反之亦然。
设计原因:
- 避免数据复制,提高性能——如果
subList
复制数据,创建新List
,将导致额外的时间和空间开销。 - 快速分页、切片——
subList
只维护索引偏移量,O(1)
时间获取子列表,适用于大数据量操作。 - 统一管理数据——修改
subList
直接影响原List
,确保数据一致性。
注意事项:
subList
不是独立List
,不要长期持有,可能导致内存泄漏。- 删除
subList
元素后,访问原List
可能抛ConcurrentModificationException
。 - 如果需要真正的副本,必须
new ArrayList<>(subList)
。
# 答案解析
# 1. subList 的底层原理
在 AbstractList
(ArrayList
的父类)中,subList()
直接创建 SubList
视图,其核心逻辑:
- 不复制数据,仅存储原
List
引用、起始索引、长度 - 所有操作都映射到原
List
,如get()
、set()
、remove()
modCount
监测修改,防止并发修改异常
核心源码(JDK 18 ArrayList#subList()
):
public List<E> subList(int fromIndex, int toIndex) {
rangeCheck(fromIndex, toIndex, size); // 检查索引合法性
return new SubList(this, fromIndex, toIndex);
}
1
2
3
4
2
3
4
SubList 结构:
private static class SubList<E> extends AbstractList<E> {
private final AbstractList<E> parent; // 直接引用原 List
private final int offset; // 起始索引
private int size; // 子列表大小
public E get(int index) {
return parent.get(index + offset); // 访问原 List 数据
}
public void set(int index, E element) {
parent.set(index + offset, element); // 修改原 List 数据
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
示例:
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> subList = list.subList(1, 4); // [2, 3, 4]
subList.set(0, 99);
System.out.println(list); // [1, 99, 3, 4, 5]
1
2
3
4
2
3
4
✅ subList
修改,list
也发生变化,因为它们共享底层数组!
# 2. 为什么 subList 不能是独立的 List?
# (1)避免数据拷贝,提高性能
如果 subList()
创建新 List
并复制数据:
public List<E> subList(int fromIndex, int toIndex) {
return new ArrayList<>(super.subList(fromIndex, toIndex));
}
1
2
3
2
3
- 缺点:会导致额外的内存占用 +
O(n)
复制开销 - 影响:如果原
List
有100W+
数据,每次subList()
都会复制一部分,性能下降
所以,Java 选择返回视图,避免不必要的内存开销和时间损耗。
# (2)支持大规模数据操作
在大数据场景(如分页查询),如果 subList
复制数据,每次查询都要遍历并复制大数组,会严重影响性能。
返回视图可以让 subList()
在 O(1) 时间内获取子列表,即使数据量巨大,也能快速切片。
# (3)子列表和原列表的同步性
如果 subList()
生成独立 List
,修改 subList
不会影响原 List
,导致数据不一致:
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> copyList = new ArrayList<>(list.subList(1, 4));
copyList.set(0, 99);
System.out.println(list); // [1, 2, 3, 4, 5] (没有变化)
1
2
3
4
2
3
4
但 subList
是视图,确保修改同步:
List<Integer> subList = list.subList(1, 4);
subList.set(0, 99);
System.out.println(list); // [1, 99, 3, 4, 5] (修改生效)
1
2
3
2
3
# 3. subList 的常见坑
# (1)删除 subList 影响原 List
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> subList = list.subList(1, 4);
subList.clear();
System.out.println(list); // [1, 5]
1
2
3
4
2
3
4
✅ subList.clear()
会删除原 List
的数据!
# (2)subList 后原 List 不能修改
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> subList = list.subList(1, 4);
list.remove(2); // 删除 3
System.out.println(subList); // ConcurrentModificationException!
1
2
3
4
2
3
4
subList
依赖原 List
的 modCount
,修改后检测到 modCount
变化,直接抛异常。
# (3)避免 subList 长期持有,防止内存泄漏
subList()
持有原 List
的引用,如果 subList
在长期任务中持有,但原 List
没有释放,会导致内存泄漏。
# 4. subList 和其他方式的对比
方式 | 是否拷贝数据 | 线程安全 | 适用场景 |
---|---|---|---|
subList() | ❌ 仅创建视图 | ❌ 受原 List 影响 | 大数据分页、高效拆分 |
stream().skip().limit() | ✅ 生成新流 | ✅ | 流式计算,避免共享 |
for + addAll() | ✅ 复制数据 | ✅ | 独立副本,避免影响原数据 |
# 深入追问
subList
在LinkedList
中性能如何?- 如何在
CopyOnWriteArrayList
中安全使用subList()
? subList()
为什么会引发ConcurrentModificationException
?
# 相关面试题
subList()
为什么比for
循环切片更高效?- 如何在分页查询中正确使用
subList()
? - 如何保证
subList()
操作不会影响原List
的完整性?