本文以 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; AtomicLong seed = this.seed; do { oldseed = seed.get(); nextseed = (oldseed * multiplier + addend) & mask; } while (!seed.compareAndSet(oldseed, nextseed)); 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++) { 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
| public static double random() { return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble(); }
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(); } int randomNum = (int) (Math.random() * totalWeight); 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>{ 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
|
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(); }
|