zoukankan      html  css  js  c++  java
  • 伪随机数



    笔者最近在练习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加密
    


  • 相关阅读:
    从零开始学Go之函数(一):匿名函数与闭包
    从零开始学Go之容器(四):列表
    从零开始学Go之容器(三):映射
    从零开始学Go之容器(二):切片
    从零开始学Go之容器(一):数组
    从零开始学Go之基本(五):结构体
    从零开始学Go之基本(四):流程控制语句
    从零开始学Go之基本(三):类型转换与常量
    Java 单例设计模式
    Java 数组
  • 原文地址:https://www.cnblogs.com/Howlet/p/12319487.html
Copyright © 2011-2022 走看看