Collections 工具类 15 个常用方法源码:sort、binarySearch、reverse、shuffle、unmodifiableXxx
关键词: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)
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> void sort(List<T> list) {Object[] a = list.toArray(); // ① 转数组Arrays.sort(a); // ② TimSortListIterator<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)
static void sort(int[] a, int left, int right) {// 双轴快排 DualPivotQuicksort
}
原生类型用双轴快排;对象类型用 TimSort。
TimSort 关键逻辑(行号 389)
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)
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); // 索引访问elsereturn iteratorBinarySearch(list, key); // 迭代器访问
}
返回值:
< 0
表示-(插入点) - 1
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)
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)
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)
public static <T> List<T> unmodifiableList(List<? extends T> list) {return (list instanceof RandomAccess ?new UnmodifiableRandomAccessList<>(list) :new UnmodifiableList<>(list));
}
UnmodifiableList 典型方法(行号 1276)
public boolean add(E e) {throw new UnsupportedOperationException(); // 只读
}
代理模式:所有写方法直接抛异常;读方法转发给底层 list。
5. 实战复现:4 段代码 + 坑位演示
5.1 sort 对自定义对象排序
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 获取插入点
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 公平性验证
List<Integer> list = IntStream.rangeClosed(1, 5).boxed().collect(Collectors.toList());
Collections.shuffle(list, new Random(42));
System.out.println(list); // 相同种子可重复
5.4 unmodifiableList 反射陷阱
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 视图坑吗?评论区贴出异常堆栈 / 分布图,一起源码级排查!