Collections.shuffle()采用Fisher-Yates算法确保n!种排列等概率,依列表类型与大小自动选择原地交换或数组中转策略,支持自定义Random源,要求列表可修改。

Collections.shuffle() 表面只是一行调用,实际是兼顾公平性、性能与兼容性的精密设计。它不简单地随机交换,而是依据列表类型和大小自动选择最优路径,核心目标是让每一种排列出现概率严格相等,同时避免 LinkedList 等结构的性能灾难。
算法根基:Fisher-Yates(Knuth)洗牌
该方法从后往前遍历,对位置 i(从末尾开始,直到索引 1)随机选取 [0, i] 范围内的一个索引 j,然后交换两者。这种单向遍历+范围受限交换,数学上可证明所有 n! 种排列等概率出现,无偏倚、不可预测、不可逆推。
- 不是“两两随机互换若干次”,那种方式会导致分布不均
- 每次只处理一个位置,且候选池随进度收缩,确保每个元素被选中的机会完全独立且均匀
- 时间复杂度稳定为 O(n),空间上仅需常数额外变量
执行策略:小列表原地交换,大列表转数组
JDK 并未对所有 List 一视同仁。它通过两个条件动态决策:
- 列表大小是否小于阈值 SHUFFLE_THRESHOLD = 5
- 列表是否实现 RandomAccess 接口(如 ArrayList、Vector)
满足任一条件即走“原地交换”路径:直接在 List 上反复调用 set() 和 get() 完成 swap;否则进入“数组中转”路径:先 toArray(),在数组上高效 shuffle(O(1) 随机访问),再用 ListIterator.set() 逐个写回。这对 LinkedList 至关重要——避免了每次 get(i) 的 O(n) 遍历开销。
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
立即学习“Java免费学习笔记(深入)”;
随机源控制:默认 vs 自定义
基础重载 shuffle(List) 使用内部共享的 Random 实例,适合日常使用;进阶重载 shuffle(List, Random) 允许传入带种子的 Random 对象。
- 固定种子(如 new Random(123))可复现相同打乱结果,对单元测试、游戏存档、可验证抽奖逻辑必不可少
- 多线程环境下,若多个线程共用同一 Random 实例,可能因竞争影响随机质量;建议每个线程使用独立 Random 或使用 ThreadLocalRandom
- 不推荐用 System.currentTimeMillis() 作种子——毫秒级精度在高并发下极易重复
注意事项与常见陷阱
该方法要求 List 可修改。以下情况会抛出异常:
- 传入 Arrays.asList() 返回的不可变列表:底层是固定大小数组,set() 操作抛 UnsupportedOperationException
- 传入 Collections.unmodifiableList() 包装的视图:任何修改操作均被拦截
- 传入空列表或单元素列表:安全,直接返回,不执行循环
若需对不可变集合洗牌,应先复制为可变 ArrayList:new ArrayList(originalList),再 shuffle。

















