0

0

Java中Collections.shuffle方法的应用

P粉602998670

P粉602998670

发布时间:2025-09-21 20:51:01

|

268人浏览过

|

来源于php中文网

原创

Collections.shuffle方法通过Fisher-Yates算法实现,使用默认或自定义Random实例打乱List顺序,确保均匀随机排列,适用于可重现测试与多场景需求。

java中collections.shuffle方法的应用

Java中的

Collections.shuffle
方法,简单来说,就是用来随机打乱一个
List
集合中元素的顺序。它能让你在需要不确定序列的场景下,快速获得一个随机排列的列表。

解决方案

Collections.shuffle
方法提供了一种非常便捷的方式来对Java中的
List
进行随机重排序。它有两个重载形式:

  1. public static void shuffle(List list)
    : 这是最常用的一个。它会使用一个默认的、系统生成的伪随机数源(通常是基于当前时间戳初始化的
    java.util.Random
    实例)来打乱传入的
    List
  2. public static void shuffle(List list, Random rnd)
    : 这个版本允许你传入一个自定义的
    java.util.Random
    实例。这在需要控制随机性(比如为了测试可重现性)或者使用特定随机数生成算法时非常有用。

无论使用哪个版本,

shuffle
方法都会直接修改传入的
List
,使其元素顺序被打乱。它不会创建新的
List
对象。从底层实现来看,它基于Fisher-Yates(或者说是Knuth shuffle)算法的一个变种,确保了每个元素在任何位置出现的概率都是均等的,也就是所谓的“均匀随机排列”。

举个例子,如果你有一个包含数字1到5的列表,调用

shuffle
之后,它可能会变成3, 1, 5, 2, 4,或者其他任何随机组合。

立即学习Java免费学习笔记(深入)”;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class ShuffleExample {
    public static void main(String[] args) {
        // 示例1: 使用默认随机源
        List cards = new ArrayList<>();
        for (int i = 1; i <= 5; i++) {
            cards.add("Card " + i);
        }
        System.out.println("原始列表: " + cards);

        Collections.shuffle(cards);
        System.out.println("默认打乱后: " + cards);

        // 示例2: 使用自定义随机源(固定种子,用于可重现性)
        List numbers = new ArrayList<>();
        for (int i = 1; i <= 5; i++) {
            numbers.add(i);
        }
        System.out.println("原始数字列表: " + numbers);

        // 使用固定种子,每次运行结果相同
        Random reproducibleRandom = new Random(12345L); 
        Collections.shuffle(numbers, reproducibleRandom);
        System.out.println("固定种子打乱后: " + numbers);

        // 再次使用相同种子,验证结果一致
        reproducibleRandom = new Random(12345L);
        List anotherNumbers = new ArrayList<>();
        for (int i = 1; i <= 5; i++) {
            anotherNumbers.add(i);
        }
        Collections.shuffle(anotherNumbers, reproducibleRandom);
        System.out.println("再次固定种子打乱后: " + anotherNumbers);
    }
}

Collections.shuffle 方法是如何确保随机性的?它背后的原理是什么?

谈到

Collections.shuffle
的随机性,我们不得不提Fisher-Yates(或称Knuth shuffle)算法,这是其核心。我个人觉得,理解这个算法对于我们信任
shuffle
的随机性至关重要。它的基本思想其实很简单,却异常巧妙:从列表的最后一个元素开始,将其与列表中任意一个位置(包括它自己)的元素进行交换。然后,对倒数第二个元素重复这个过程,这次是与前面未处理的元素中的任意一个进行交换,依此类推,直到列表的第一个元素。

具体步骤可以这样理解:

  1. 从列表的末尾(索引
    n-1
    )开始,直到列表的开头(索引
    1
    )。
  2. 在每次迭代中,选择一个随机索引
    j
    ,这个
    j
    的范围是从
    0
    到当前迭代的索引
    i
    (包含
    i
    )。
  3. 交换当前索引
    i
    的元素和随机索引
    j
    的元素。

这种方法确保了每个元素在每个位置都有相等的概率出现,从而生成一个均匀分布的随机排列。它的时间复杂度是O(N),其中N是列表的大小,效率非常高。

至于随机数的来源,默认情况下

Collections.shuffle
会使用
java.util.Random
Random
类生成的是伪随机数,这意味着它们是由一个确定性算法生成的,只是看起来随机。如果你用相同的种子(seed)初始化两个
Random
实例,它们将生成完全相同的随机数序列。对于大多数应用场景,这种伪随机性已经足够了。但在某些需要更高安全级别或更不可预测性的场合,比如密码学应用,可能就需要考虑
java.security.SecureRandom
了,尽管
Collections.shuffle
直接使用
SecureRandom
的情况并不常见,因为它会带来性能开销。

在使用 Collections.shuffle 时,我应该注意哪些潜在的性能问题或线程安全考量?

在使用

Collections.shuffle
时,性能和线程安全确实是两个值得我们思考的点。这就像在厨房里做饭,你得考虑食材处理的速度,还得注意别烫着手。

性能角度看,

Collections.shuffle
的算法复杂度是O(N),N是列表的元素数量。这意味着,列表越大,打乱所需的时间就越长,但增长是线性的。对于大多数我们日常处理的列表(比如几百、几千甚至几万个元素),这个性能开销通常可以忽略不计。我的经验是,除非你的列表有数百万甚至上亿个元素,或者你在一个极度性能敏感的循环中频繁调用它,否则你不太可能遇到显著的性能瓶颈。真正的瓶颈往往出在列表的创建、元素的添加或后续处理上,而不是
shuffle
本身。当然,如果列表是
LinkedList
而不是
ArrayList
,由于
LinkedList
随机访问元素的效率较低(O(N)),每次
get(j)
set(j, element)
操作都会比较慢,这会导致整个
shuffle
过程的效率下降到O(N^2)。所以,强烈建议对
ArrayList
或实现了
RandomAccess
接口的
List
类型使用
shuffle
,效率会高很多。

简单好用的鼠标悬停动画按钮
简单好用的鼠标悬停动画按钮

鼠标放上去会有一个很好的动画,可以自己订制,使用的方法非常简单,只需要看代码中的例子就很容易明白

下载

再说说线程安全

Collections.shuffle
方法本身并不是线程安全的。它会直接修改传入的
List
对象。这意味着,如果多个线程同时对同一个
List
调用
shuffle
,或者一个线程在
shuffle
时另一个线程在修改(添加、删除、更新)这个
List
,就可能导致不可预测的结果,甚至抛出
ConcurrentModificationException
。这是Java集合框架中常见的“快速失败”(fail-fast)机制的一部分。

那么,如何处理呢?

  • 如果每个线程处理自己的

    List
    :那完全没问题,各自独立,互不影响。

  • 如果多个线程需要共享同一个

    List
    并对其进行
    shuffle
    :你就需要外部同步机制了。最直接的方式是使用
    synchronized
    关键字来保护对
    shuffle
    方法的调用,或者使用
    java.util.concurrent.locks.Lock

    List sharedList = Collections.synchronizedList(new ArrayList<>());
    // ... 添加元素到sharedList
    
    // 在多线程环境中,需要额外的同步
    synchronized (sharedList) {
        Collections.shuffle(sharedList);
    }

    或者,如果你使用了

    java.util.ArrayList
    但希望在多线程环境下进行
    shuffle
    ,你也可以直接在调用
    shuffle
    前后进行同步:

    List myUnsynchronizedList = new ArrayList<>();
    // ... 添加元素
    
    Object lock = new Object(); // 或者直接用myUnsynchronizedList作为锁对象
    synchronized (lock) {
        Collections.shuffle(myUnsynchronizedList);
    }

    此外,如果你传入了自定义的

    Random
    实例,还需要考虑这个
    Random
    实例的线程安全性。
    java.util.Random
    是线程安全的,但如果多个线程共享同一个
    Random
    实例,并且对性能有极高要求,可以考虑使用
    java.util.concurrent.ThreadLocalRandom
    ,它能为每个线程提供独立的
    Random
    实例,从而减少竞争,提高并发性能。

如果我想实现一个可重现的随机序列,或者需要自定义随机源,Collections.shuffle 提供了哪些选项?

有时候,我们需要的“随机”并不是真正意义上的不可预测,而是希望在特定条件下能够重现相同的随机序列。这在测试、模拟或者调试时非常有用。

Collections.shuffle
的第二个重载方法就是为此而生,它允许我们传入一个自定义的
java.util.Random
实例。

实现可重现的随机序列: 核心在于

Random
类的构造函数。
Random
类有一个接受
long
类型参数的构造函数:
Random(long seed)
。这里的
seed
(种子)就是生成随机数序列的起点。如果你每次都用相同的
seed
来创建一个
Random
实例,那么这个
Random
实例生成的随机数序列将是完全一样的。 所以,要实现可重现的随机序列,你只需要:

  1. 创建一个
    Random
    实例,并传入一个固定的
    long
    值作为种子。
  2. 将这个
    Random
    实例作为第二个参数传递给
    Collections.shuffle
    方法。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class ReproducibleShuffle {
    public static void main(String[] args) {
        long fixedSeed = 98765L; // 这是一个固定的种子

        List items1 = new ArrayList<>();
        items1.add("A"); items1.add("B"); items1.add("C"); items1.add("D"); items1.add("E");

        List items2 = new ArrayList<>();
        items2.add("A"); items2.add("B"); items2.add("C"); items2.add("D"); items2.add("E");

        System.out.println("原始列表1: " + items1);
        System.out.println("原始列表2: " + items2);

        // 使用相同的种子打乱列表1
        Random random1 = new Random(fixedSeed);
        Collections.shuffle(items1, random1);
        System.out.println("使用固定种子打乱列表1: " + items1);

        // 再次使用相同的种子打乱列表2
        Random random2 = new Random(fixedSeed); // 重新创建一个Random实例,使用相同的种子
        Collections.shuffle(items2, random2);
        System.out.println("使用相同固定种子打乱列表2: " + items2);

        // 结果会是一样的,因为种子相同
    }
}

在我看来,这种能力在单元测试中特别有用。比如,你测试一个依赖于随机排序的算法,如果每次测试结果都不同,调试起来会很麻烦。通过固定种子,你可以确保每次运行测试时,

shuffle
的结果都是一样的,从而更容易定位问题。

自定义随机源: 除了固定种子,传入自定义

Random
实例的另一个好处是你可以使用不同类型的随机数生成器。虽然
java.util.Random
对于大多数应用已经足够,但在某些特殊场景下,你可能需要:

  • 更强的随机性:例如,在一些安全敏感的应用中,你可能希望使用
    java.security.SecureRandom
    SecureRandom
    提供了加密级别的强随机数,其生成速度通常比
    Random
    慢,但其输出更难以预测。不过,正如前面提到的,直接将
    SecureRandom
    用于
    Collections.shuffle
    并不常见,因为它会带来额外的性能开销,而通常
    shuffle
    的随机性要求达不到密码学级别。
  • 自定义伪随机算法:虽然Java标准库提供了
    Random
    ,但理论上你也可以实现自己的
    Random
    子类,只要它遵循
    Random
    的契约。这在一些学术研究或特定模拟场景中可能会用到,尽管在实际开发中很少见。

总的来说,

Collections.shuffle
的灵活性在于它将随机数生成与列表打乱逻辑解耦。你可以根据自己的需求,选择默认的、可重现的,甚至是更高级的随机数源,来满足不同的应用场景。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
javascriptvoid(o)怎么解决
javascriptvoid(o)怎么解决

javascriptvoid(o)的解决办法:1、检查语法错误;2、确保正确的执行环境;3、检查其他代码的冲突;4、使用事件委托;5、使用其他绑定方式;6、检查外部资源等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

177

2023.11.23

java中void的含义
java中void的含义

本专题整合了Java中void的相关内容,阅读专题下面的文章了解更多详细内容。

98

2025.11.27

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1130

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

213

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1719

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

20

2026.01.19

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

503

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

166

2025.12.24

clawdbot ai使用教程 保姆级clawdbot部署安装手册
clawdbot ai使用教程 保姆级clawdbot部署安装手册

Clawdbot是一个“有灵魂”的AI助手,可以帮用户清空收件箱、发送电子邮件、管理日历、办理航班值机等等,并且可以接入用户常用的任何聊天APP,所有的操作均可通过WhatsApp、Telegram等平台完成,用户只需通过对话,就能操控设备自动执行各类任务。

8

2026.01.29

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 3万人学习

C# 教程
C# 教程

共94课时 | 7.9万人学习

Java 教程
Java 教程

共578课时 | 52.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号