伪随机数

Posted howlet

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了伪随机数相关的知识,希望对你有一定的参考价值。



笔者最近在练习mysql语句优化,奈何年少不懂,找不到百万级别的测试数据,只好用java随机生成数据凑合用一下,所以写下此篇博客,经测试生成500万条数据后台用了9秒,完全可以接受




1. Random

random伪随机数类在 java.util 包下,是最常用的随机数生成器,其使用线性同余公式来生成随机数,所以才说是伪随机。该类的实例是线程安全的,多线程并发使用可能会遇到争用问题,这时可用 ThreadLocalRandom 来解决这个问题,此外还有 SecureRandom 、SplittableRandom 随机生成器,这里就不扩展说明了




2. 构造方法与常用方法

类型 名字 解释
Random() 默认构造函数
Random(long seed) 有参构造,用种子创建伪随机生成器
int nextInt 返回生成器中生成表序列中的下一个伪随机数
int nextInt(int n) 返回均匀分布于区间 [0,n)的伪随机数
double nextDouble 返回下一个伪随机数 [0.0,1.0)




3. 具体分析


先看无参构造,直接上源码

// 无参构造也是调用有参构造的,那么放出有参构造,再看里面具体内容
public Random() {
    this(seedUniquifier() ^ System.nanoTime());
}


// 有参构造接收长整型种子参数
public Random(long seed) {
    // 判断是否本类
    if (getClass() == Random.class)
        // 可以看出长整型种子是Atomic原子型的,即线程安全
        // initialScramble() 是seed与两个具体数值运算,这里不给出了
        this.seed = new AtomicLong(initialScramble(seed));
    else {
        // subclass might have overriden setSeed
        // 翻译:子类可能重写setSeed方法
        this.seed = new AtomicLong();
        setSeed(seed);
    }
}


// 再回无参构造内部
// 其中 ^ System.nanoTime() 表示与系统纳秒异或运算,也就是说随机数依赖于时间
this(seedUniquifier() ^ System.nanoTime());

private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);
private static long seedUniquifier() {
    // L'Ecuyer, "Tables of Linear Congruential Generators of
    // Different Sizes and Good Lattice Structure", 1999
    // 翻译:不同大小结构良好的线性同余生成元表,
    for (;;) {
        long current = seedUniquifier.get();
        long next = current * 181783497276652981L;
        // 用到了CAS轻量锁
        if (seedUniquifier.compareAndSet(current, next))
            return next;
    }
}


再看nextInt方法,有参的方法用逻辑运算把范围指定,这里就不介绍了

public int nextInt() {
    return next(32);
}

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 r1 = new Random();
Random r2 = new Random();
Random r3 = new Random();
Random r4 = new Random(1000);
Random r5 = new Random(1000);

System.out.println(r1.nextInt());
System.out.println(r2.nextInt());
System.out.println(r3.nextInt(100));
System.out.println(r4.nextInt());
System.out.println(r5.nextInt());
491030142
2021835847
49
-1244746321
-1244746321


从结果和源码可以看出:

  • 这里补充一下seed是final类型,线程更安全
  • 给定seed之后,伪随机数的序列是确定的
  • 而没有给seed因为依赖于变化的时间,所以每次的序列是不确定的
  • 常用 new Random.nextInt(int n)来生成伪随机数




4. Math.random


我们最常用还是这个函数,静态调用方便简单

// 底层还是用了Random类
public static double random() {
    return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}

// 新建一个依赖时间的随机数生成器
private static final class RandomNumberGeneratorHolder {
    static final Random randomNumberGenerator = new Random();
}

// 位运算加强随机
public double nextDouble() {
    return (((long)(next(26)) << 27) + next(27)) * DOUBLE_UNIT;
}

从源码可以看出:

  • 这个类方便我们使用伪随机数,每次调用就新建一个Random类
  • 也知道区间为 [0.0,1.0)


生成给定范围的伪随机数

// 给定范围
int min = 10;
int max = 15;

// 生成伪随机小数
double num = Math.random();

// 范围逻辑运算,想一下很简单的
int rs = (int)(num * (max - min + 1) + min);
        
System.out.println(rs); // 需要整数的位数




5. 这里贴一下生成测试数据中密码的逻辑

// 密码字符范围
String range = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~!@#$%^&*()_+[];',.<>?:{}|";

// 生成100个伪随机密码
for(int i = 0; i < 100; i++){
    
    // 字符串
    StringBuffer bf = new StringBuffer();
    
    // 密码长度8~20
    int len = (int)(Math.random() * (20 - 8 + 1) + 8);
    for(int j = 0; j < len; j++){
        int index = new Random().nextInt(range.length());
        bf.append(range.charAt(index));
    }
    System.out.println(bf.toString());
}
_ho1O@<s
|4z$1sDIDRt_o{PR
H_}z;A9;K74amjb2r
O;*89#b!|4w|;z?~
s+EmeTCdpJ9?W8,lNNl|
o2#P9R@,hFT
{+})BECM.Jf|&
// 完全看不懂,还可以加上MD5加密




以上是关于伪随机数的主要内容,如果未能解决你的问题,请参考以下文章

伪代码

伪随机目录树生成?

一个简单的伪随机数发生算法(转)

php伪随机数漏洞 以及脚本php_mt_seed的使用教程

播放随机声音而不重复

Random类