Collections 工具类 15 个常用方法源码:sort、binarySearch、reverse、shuffle、unmodifiableXxx

关键词:Collections 工具类、sort、binarySearch、reverse、shuffle、unmodifiableXxx、TimSort、源码、面试

适合人群:Java 初中高级工程师 · 面试冲刺 · 代码调优 · 架构设计

阅读时长:35 min(≈ 5500 字)

版本环境:JDK 17(源码行号对应 jdk-17+35)


1. 开场白:面试四连击,答不出就挂

  1. "Collections.sort 用的是哪种排序算法?时间复杂度多少?"
  2. "binarySearch 对未排序列表返回什么值?怎么计算插入点?"
  3. "shuffle 算法公平吗?如何自定义随机源?"
  4. "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() 或 Guava ImmutableList


6. 线上事故:shuffle 随机源重复导致奖品分布偏差

背景

抽奖系统用 Collections.shuffle(list) 发奖,未指定 seed,压测时复现同一序列。

现象

同一用户多次抽到同一奖品,被投诉"暗箱操作"。

根因
new Random() 种子相同(系统时间粒度粗),shuffle 序列重复。

复盘

  1. 修复:使用 SecureRandom 并每轮重新 seed。
  2. 防呆:
    • 线上 shuffle 必须 SecureRandom
    • 单元测试断言分布均匀(卡方校验)。

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 视图坑吗?评论区贴出异常堆栈 / 分布图,一起源码级排查!

相关推荐
武子康4 小时前
Java-153 深入浅出 MongoDB 全面的适用场景分析与选型指南 场景应用指南
java·开发语言·数据库·mongodb·性能优化·系统架构·nosql
rit84324994 小时前
ES6 箭头函数:告别 `this` 的困扰
开发语言·javascript·es6
嵌入式-老费4 小时前
Easyx图形库应用(用lua开发图形界面)
开发语言·lua
ellis19704 小时前
Lua协程coroutine库用法
开发语言·lua
nwsuaf_huasir4 小时前
matlab构造带通巴特沃斯滤波器进行滤波
开发语言·matlab
救救孩子把4 小时前
从 JDK 8 到 JDK 23:HotSpot 垃圾回收器全景演进与深度剖析
java·开发语言·jvm·jdk
ha20428941945 小时前
Linux操作系统学习之---线程控制
java·linux·学习
清辞8535 小时前
C++入门(底层知识C与C++的不同)
开发语言·c++·算法
Knight_AL5 小时前
Spring AOP 中@annotation的两种写法详解
java·spring