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) 遍历开销。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
随机源控制:默认 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。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










