关键词:Collections 工具类、sort、binarySearch、reverse、shuffle、unmodifiableXxx、TimSort、源码、面试
适合人群:Java 初中高级工程师 · 面试冲刺 · 代码调优 · 架构设计
阅读时长:35 min(≈ 5500 字)
版本环境:JDK 17(源码行号对应 jdk-17+35)
1. 开场白:面试四连击,答不出就挂
- "Collections.sort 用的是哪种排序算法?时间复杂度多少?"
- "binarySearch 对未排序列表返回什么值?怎么计算插入点?"
- "shuffle 算法公平吗?如何自定义随机源?"
- "unmodifiableList 返回的 List 能新增元素吗?为什么不能?"
阿里 P7 面完 100 人,能把 TimSort 归并栈、洗牌 Fisher-Yates、代理视图陷阱说透的不超过 5 个。
线上事故:某配置中心用 Collections.unmodifiableList()
包装后暴露给业务,业务反射新增元素,导致 UnsupportedOperationException
线上狂飙 5k 次,网关熔断。
背完本篇,你能手写 TimSort 归并栈、复现 Java 版洗牌、给出 3 种只读视图方案,让面试官闭嘴。
2. 知识骨架:Collections 15 核表一网打尽
方法族 | 代表方法 | 算法/实现 | 时间复杂度 | 备注 |
---|---|---|---|---|
sort | sort(List<T> list) |
TimSort | O(n log n) | 稳定 |
binarySearch | binarySearch(List<? extends Comparable> list, T key) |
二分 | O(log n) | 需有序 |
reverse | reverse(List<?> list) |
双指针交换 | O(n) | 原位 |
shuffle | shuffle(List<?> list) |
Fisher-Yates | O(n) | Random 实例 |
unmodifiableXxx | unmodifiableList(List<? extends T> list) |
代理视图 | 读 O(1) | 写抛异常 |
synchronizedXxx | synchronizedMap(Map<K,V> m) |
全对象锁 | 读写串行 | 已废弃趋势 |
emptyXxx | emptyList() |
单例空集 | O(1) | 全局不可变 |
singletonXxx | singleton(T o) |
单元素集 | O(1) | 迭代一次 |
3. 身世档案:核心常量与入口方法
常量/字段 | 含义 | 值/备注 |
---|---|---|
DEFAULT_THRESHOLD |
TimSort 小数组归并阈值 | 32 |
MIN_MERGE |
TimSort 最小归并长度 | 16 |
UNMODIFIABLE |
代理标记 | 私有静态类 |
4. 原理解码:源码逐行,行号指路
4.1 sort() → TimSort 入口(行号 234)
java
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> void sort(List<T> list) {
Object[] a = list.toArray(); // ① 转数组
Arrays.sort(a); // ② TimSort
ListIterator<T> i = list.listIterator();
for (int j=0; j<a.length; j++) {
i.next();
i.set((T)a[j]); // ③ 写回原 list
}
}
稳定排序;ArrayList 走
Arrays.sort((T[]) a)
免拷贝。
Arrays.sort(int[]) 快排 + 双轴(行号 1482)
java
static void sort(int[] a, int left, int right) {
// 双轴快排 DualPivotQuicksort
}
原生类型用双轴快排;对象类型用 TimSort。
TimSort 关键逻辑(行号 389)
java
private static <T> void mergeSort(T[] a, T[] aux, int lo, int hi, Comparator<? super T> c) {
if (hi - lo < MIN_MERGE) {
binarySort(a, lo, hi, lo, c); // 二分插入
return;
}
int mid = (lo + hi) >>> 1;
mergeSort(a, aux, lo, mid, c);
mergeSort(a, aux, mid, hi, c);
if (c.compare(a[mid-1], a[mid]) <= 0) return; // 已有序免归并
merge(a, aux, lo, mid, hi, c); // 归并
}
4.2 binarySearch() 二分 + 插入点(行号 2729)
java
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return indexedBinarySearch(list, key); // 索引访问
else
return iteratorBinarySearch(list, key); // 迭代器访问
}
返回值:
< 0
表示-(插入点) - 1
java
private static <T> int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
int low = 0;
int high = list.size() - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = list.get(mid);
int cmp = midVal.compareTo(key);
if (cmp < 0) low = mid + 1;
else if (cmp > 0) high = mid - 1;
else return mid;
}
return -(low + 1); // 插入点
}
4.3 reverse() 双指针交换(行号 2437)
java
public static void reverse(List<?> list) {
int size = list.size();
if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) {
for (int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)
swap(list, i, j);
} else {
ListIterator fwd = list.listIterator();
ListIterator rev = list.listIterator(size);
for (int i=0, mid=list.size()>>1; i<mid; i++) {
Object tmp = fwd.next();
fwd.set(rev.previous());
rev.set(tmp);
}
}
}
REVERSE_THRESHOLD = 256
;RandomAccess 用索引,否则用迭代器对称交换。
4.4 shuffle() Fisher-Yates(行号 2671)
java
public static void shuffle(List<?> list, Random rnd) {
int size = list.size();
if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {
for (int i=size; i>1; i--)
swap(list, i-1, rnd.nextInt(i)); // 经典洗牌
} else {
Object arr[] = list.toArray(); // 先转数组
for (int i=size; i>1; i--)
swap(arr, i-1, rnd.nextInt(i));
ListIterator it = list.listIterator();
for (Object e : arr) {
it.next();
it.set(e);
}
}
}
公平性:每个排列概率 1/n!;可传入
new Random(seed)
复现序列。
4.5 unmodifiableList() 代理视图(行号 1324)
java
public static <T> List<T> unmodifiableList(List<? extends T> list) {
return (list instanceof RandomAccess ?
new UnmodifiableRandomAccessList<>(list) :
new UnmodifiableList<>(list));
}
UnmodifiableList 典型方法(行号 1276)
java
public boolean add(E e) {
throw new UnsupportedOperationException(); // 只读
}
代理模式:所有写方法直接抛异常;读方法转发给底层 list。
5. 实战复现:4 段代码 + 坑位演示
5.1 sort 对自定义对象排序
java
List<Student> list = Arrays.asList(new Student("Tom", 20), new Student("Alice", 18));
Collections.sort(list, Comparator.comparingInt(Student::getAge));
System.out.println(list); // [Alice(18), Tom(20)]
5.2 binarySearch 获取插入点
java
List<Integer> sorted = Arrays.asList(1, 3, 5, 7);
int idx = Collections.binarySearch(sorted, 6); // 返回 -4
int insert = -(idx + 1); // 3
sorted.add(insert, 6); // [1, 3, 5, 6, 7]
5.3 shuffle 公平性验证
java
List<Integer> list = IntStream.rangeClosed(1, 5).boxed().collect(Collectors.toList());
Collections.shuffle(list, new Random(42));
System.out.println(list); // 相同种子可重复
5.4 unmodifiableList 反射陷阱
java
List<String> inner = new ArrayList<>();
inner.add("A");
List<String> outer = Collections.unmodifiableList(inner);
inner.add("B"); // 底层 list 变化
System.out.println(outer); // [A, B] --- 视图实时可见
outer.add("C"); // UnsupportedOperationException
只读代理禁止通过 outer 写,但底层 inner 仍可变;真正不可变需
List.copyOf()
或 GuavaImmutableList
。
6. 线上事故:shuffle 随机源重复导致奖品分布偏差
背景
抽奖系统用 Collections.shuffle(list)
发奖,未指定 seed,压测时复现同一序列。
现象
同一用户多次抽到同一奖品,被投诉"暗箱操作"。
根因
new Random()
种子相同(系统时间粒度粗),shuffle 序列重复。
复盘
- 修复:使用
SecureRandom
并每轮重新 seed。 - 防呆:
- 线上 shuffle 必须
SecureRandom
; - 单元测试断言分布均匀(卡方校验)。
- 线上 shuffle 必须
7. 面试 10 连击:答案 + 行号
问题 | 答案 |
---|---|
1. Collections.sort 用哪种算法? | TimSort(行号 234) |
2. 时间复杂度? | O(n log n) 稳定 |
3. binarySearch 返回值含义? | ≥0 索引;<0 插入点 -(idx+1) (行号 2737) |
4. reverse 对链表怎么实现? | 双向迭代器对称交换(行号 2445) |
5. shuffle 公平性? | Fisher-Yates,每种排列 1/n!(行号 2673) |
6. unmodifiableList 能否新增? | 不能,抛 UnsupportedOperationException(行号 1276) |
7. 底层 list 修改后视图可见吗? | 可见,代理实时转发 |
8. 如何真正不可变? | List.copyOf() 或 Guava ImmutableList |
9. synchronizedMap 锁粒度? | 全对象 mutex(行号 1685) |
10. sort 对 ArrayList 优化? | 直接 Arrays.sort((T[]) a) 免拷贝(行号 240) |
8. 总结升华:一张脑图 + 三句话口诀
[脑图文字版]
中央:Collections 工具
├─sort:TimSort 稳定 n log n
├─binarySearch:插值 -(idx+1)
├─reverse:双指针交换
├─shuffle:Fisher-Yates
└─unmodifiable:代理只读
口诀:
"Tim 归并稳定快,二分负值插位来;洗牌公平加种子,只读代理底层开。"
9. 下篇预告
阶段 3 第一炮《ConcurrentHashMap 1.7 与 1.8 源码对比:分段锁 → CAS + synchronized》将带你手绘分段锁内存图、CAS 无锁化、sizeCtl 魔法,敬请期待!
10. 互动专区
你在生产环境踩过 shuffle 随机源或 unmodifiable 视图坑吗?评论区贴出异常堆栈 / 分布图,一起源码级排查!