collections.shuffle() 不适合小样本采样,因其需 o(n) 时间打乱全部元素,而实际只需 o(k);推荐根据场景选用 threadlocalrandom.ints().distinct().limit(k) 或蓄水池算法。

为什么 Collections.shuffle() 不适合小样本随机采样
它会完整打乱整个集合,哪怕你只要 3 个元素,也得对 10 万条数据做 O(n) 洗牌——时间和空间开销都浪费在多余操作上。
常见错误现象:shuffle(list); return list.subList(0, k); 在大数据量下明显卡顿,GC 压力大;并发环境下还可能因修改原集合引发意外副作用。
- 适用场景:需要全部重排、或后续还要用完整乱序结果
- 不适用场景:仅取 k 个不重复随机项(尤其 k ≪ n)
- 性能影响:时间复杂度从 O(k) 恶化为 O(n),内存额外占用 O(n)
用 Random.nextInt() 配合 Set 去重的陷阱
很多人直接写循环 + nextInt(n) + Set.add(),看似简单,但当 k 接近 n 时,碰撞概率飙升,实际运行时间可能指数级增长。
比如从 1000 个元素里抽 990 个,后期每次生成新索引平均要试 10+ 次才能成功。
- 容易踩的坑:没设最大重试次数,极端情况陷入长循环甚至死循环
- 兼容性没问题,但不可控的延迟会让接口响应抖动
- 正确做法是加一个 fallback:当重试超过
k * 2次,自动切到shuffle路径
推荐方案:ReservoirSampling 适合流式或未知长度场景
如果你的数据来自数据库游标、文件逐行读取、或 API 分页流,根本不知道总长度,那就别硬算 size——蓄水池算法天然适配。
它只需遍历一次,内存固定 O(k),且概率严格均匀。Java 里没有内置,但实现就 10 行左右:
Random r = new Random();
List<T> reservoir = new ArrayList<>(k);
for (int i = 0; i < k && it.hasNext(); i++) {
reservoir.add(it.next());
}
for (int i = k; it.hasNext(); i++) {
T item = it.next();
int j = r.nextInt(i + 1);
if (j < k) reservoir.set(j, item);
}- 使用场景:数据不可随机访问、长度未知、或内存受限
- 注意点:
i必须从 0 开始计数,且r.nextInt(i + 1)不能写成r.nextInt(i) - 性能稳定:O(n) 时间,O(k) 空间,无碰撞风险
已知长度时最简可靠的写法:ThreadLocalRandom.current().ints()
JDK 8+ 提供了真正为并发优化的随机源,配合 distinct() 和 limit(),一行就能搞定小样本无放回采样:
List<String> sample = list.stream()
.map((s, i) -> i)
.collect(Collectors.toList())
.stream()
.mapToLong(i -> ThreadLocalRandom.current().nextLong(list.size()))
.distinct()
.limit(k)
.mapToObj(list::get)
.collect(Collectors.toList());等等——上面那段其实不对,mapToLong 后没法再 distinct 索引再映射……真实可用的是这个:
int n = list.size();
List<Integer> indices = ThreadLocalRandom.current()
.ints(0, n)
.distinct()
.limit(k)
.boxed()
.collect(Collectors.toList());
List<String> sample = indices.stream().map(list::get).collect(Collectors.toList());- 关键点:
ints(0, n)生成的是 [0, n) 区间整数,不是nextLong(n) - 为什么用
ThreadLocalRandom:避免多线程争用Random全局锁 - 限制:k 接近 n 时仍可能变慢,但 JDK 会自动优化
distinct()的底层实现,比手写 while 循环更稳
实际用的时候,别光看“简洁”,先想清楚你的数据规模、是否复用、是否并发——k 小就用随机索引,k 大就 shuffle,流式就上蓄水池。边界条件比算法本身更容易出错。










