collections.shuffle 使用 fisher-yates 算法从后往前遍历,每次在 [0,i] 随机选 j 交换,确保每种排列概率均等;自动适配 randomaccess 或 linkedlist 结构优化性能;默认用 threadlocalrandom,支持自定义种子;需传入可修改 list,避免 arrays.aslist() 等不可变视图。

Collections.shuffle 不是“随机挑两个换位置”,而是用数学上可证明公平的 Fisher-Yates 算法,从后往前逐个固定元素,确保每一种排列出现概率完全相等。
算法本质:从后往前的确定性交换
它不靠多次随机交换凑效果,而是严格按以下步骤执行:
- 从最后一个索引(n−1)开始,向前遍历到索引 1(不含 0)
- 对当前索引 i,在区间 [0, i] 内均匀随机选一个 j
- 交换位置 i 和 j 的元素
- 这样第 i 位被任意未固定元素填入的概率恒为 1/(i+1),整条路径共 n! 条,一一对应唯一排列
这种设计杜绝了“nⁿ 条路径无法整除 n!”导致的分布偏斜——这是手写随机交换最容易踩的坑。
执行策略:按列表类型自动适配
它会现场判断结构特征,选择最省时的路径:
- 若列表实现 RandomAccess(如 ArrayList)或元素数 ≤ 5:直接在原 List 上调用 get()/set() 原地 swap,时间 O(n),空间 O(1)
- 若为 LinkedList 等顺序访问慢的结构:先 toArray() 转数组 → 在数组上高效 shuffle → 用 ListIterator.set() 逐个写回
此举避免了 LinkedList 中反复 get(i) 带来的 O(n²) 开销,对万级数据性能差异明显。
随机源控制:默认与自定义各司其职
两种重载签名覆盖不同场景需求:
- 无参版:内部使用 ThreadLocalRandom.current()(Java 7+),适合生产环境真随机
- 带 Random 参数版:传入 new Random(123L) 等固定种子实例,结果完全可重现,专用于测试、审计、游戏存档
注意:不要用 System.currentTimeMillis() 做种子,毫秒级精度在高并发下极易重复;多线程中避免共享同一 Random 实例。
使用前提与典型陷阱
方法看似简单,但失败往往出在准备阶段:
- 必须传入可修改的 List:Arrays.asList() 返回的是固定大小视图,直接 shuffle 会抛 UnsupportedOperationException
- 安全做法:用 new ArrayList(original) 包一层,既防异常,又隔离原始数据
- 数组、Set、Map 需先转 List:如 new ArrayList(mySet),再 shuffle
- null 元素、泛型混用不会报错,但可能引发后续逻辑异常,建议提前校验
不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











