JDK随机方法汇总

本文以 java.util.Random 类为切入点,简单的探讨了一下Random对象构造原理、成员方法的使用,以及常用随机功能的实现。旨在对随机数有个浅显的认识。水平有限,理解不到之处,还请斧正。


获取随机数

Java获取随机数有两个主要途径,分别是java.util.Random 类和 java.lang.Math.random() 方法。后者实际上调用了前者。

Random

构造方法

java.util.Random 的构造方法有两种

有参构造

seed为确定随机数的种子

源码

1
2
3
4
5
6
7
8
public Random(long seed) {
if (getClass() == Random.class)
this.seed = new AtomicLong(initialScramble(seed));
else {
this.seed = new AtomicLong();
setSeed(seed);
}
}
无参构造

调用有参构造,种子由初始化种子与系统时间进行异或运算得到。

源码

1
2
3
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}

关于异或运算请参考与运算,或运算,异或运算

常用成员方法

next(int bits)

nextXxx方法底层都是调用的next(int bits),该方法会通过旧种子去生成新种子,并将新种子赋值为旧种子。

由于使用CAS技术,多线程下只有一个线程能更新成功,其它线程只能重新获取,所以是线程安全的

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected int next(int bits) {
long oldseed, nextseed;
// this.seed是j.u.c.atomic.AtomicLong类型
AtomicLong seed = this.seed;
do {
// 旧种子
oldseed = seed.get();
// 根据旧种子生成新种子
nextseed = (oldseed * multiplier + addend) & mask;
// 用nextseed更新oldseed
} while (!seed.compareAndSet(oldseed, nextseed));
// 无符号右移(48-bits)位
return (int)(nextseed >>> (48 - bits));
}
伪随机数

由于新种子总是基于旧种子产生,所以初始种子确定时,所有新种子的值都已经确定。

因此,通过Random 获得的随机数,其实是伪随机数

验证如下:

1
2
3
4
5
6
7
8
public void test(){
Random random = new Random(47);
for (int i = 0; i < 5; i++) {
// nextInt()实际调用的是next(32)
int num = random.nextInt();
System.out.print(num + ", ");
}
}

该方法无论运行多少次,运行结果一定是[-1172028779, 1717241110, -2014573909, 229403722, 688081923]

nextInt()

底层直接将新种子作为随机数,随机数范围是[-2^31, 2^31-]

源码
1
2
3
public int nextInt() {
return next(32);
}
nextInt(int bound)

底层将新种子对bound取余作为随机数,随机数范围是[0, bound-1]

源码
1
2
3
4
5
6
7
8
9
10
11
12
public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
int r = next(31);
int m = bound - 1;
if ((bound & m) == 0)
r = (int)((bound * (long)r) >> 31);
else {
for (int u = r; u - (r = u % bound) + m < 0; u = next(31));
}
return r;
}
nextDouble()

生成随机数范围[0.0,1.0)

源码
1
2
3
public double nextDouble() {
return (((long)(next(26)) << 27) + next(27)) * DOUBLE_UNIT;
}

Math.random

Math.random 实质调用的是Random.nextDouble()方法,生成随机数范围[0.0,1.0)

源码

java.lang.Math 的静态内部类 RandomNumberGeneratorHolder 持有的静态成员变量 randomNumberGenerator,是 java.util.Random 类型

1
2
3
4
5
6
7
8
9
// 调用nextDouble()
public static double random() {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}

// randomNumberGenerator就是Random的实例
private static final class RandomNumberGeneratorHolder {
static final Random randomNumberGenerator = new Random();
}

常用随机功能实现

加权随机

加权随机可以使一个数组中各元素被选中的概率不同

需求

比如有ABCD四个选项,权重为1-4,表示被选中的概率分别为10%-40%。

选项 权重 抽中的概率
A 1 10%
B 2 20%
C 3 30%
D 4 40%

思路

  • 先将各选项的权重相加,得到总权重
  • 按权重从高到低排序
  • 获取一个介于1和总权重之间的随机数,本例中是1-10
  • 看随机数落在哪个区间,则表示抽中哪个选项

代码实现

从给定的设备列表中获取随机设备(加权随机)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private String getRandomDevice(List<Device> deviceList) {

// 计算总权重
int totalWeight = 0;
for (Device device : deviceList) {
totalWeight += device.getWeight();
}

// 先求随机数 [0-totalWeight)
int randomNum = (int) (Math.random() * totalWeight);

// 按weight从大到小排序,可减少遍历次数
Collections.sort(deviceList);

// 当随机数落在哪个区间,则选定哪个设备
int currentWeight = 0;
for (Device device : deviceList) {
currentWeight += device.getWeight();
if (currentWeight > randomNum) {
return device.getIp();
}
}
return deviceList.get(0).getIp();
}

设备对象,已实现排序规则

1
2
3
4
5
6
7
8
9
10
11
12
@Data
public class Device implements Comparable<Device>{
// 设备ip
private String ip;
// 设备权重
private Integer weight;

@Override
public int compareTo(Device d) {
return d.getWeight() - this.weight;
}
}

获取随机字符串

利用字符与数字的对应关系,可以生成ASCII码从33到126的随机可显字符串,如 OQ>wWrF0…

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @param length 指定随机数长度
*/
public static String getRandomStr(int length){
int start = 33; // '!'
int end = 126; // '~'
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
int index = (int) (Math.random() * (end - start)) + start;
sb.append((char) index);
}
return sb.toString();
}
文章作者: SongGT
文章链接: http://www.songguangtao.xyz/2022/08/03/7.JDK随机方法汇总/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 SongGuangtao's Blog
大哥大嫂[微信打赏]
过年好[支付宝打赏]