僞隨機數



筆者最近在練習Mysql語句優化,奈何年少不懂,找不到百萬級別的測試數據,只好用java隨機生成數據湊合用一下,因此寫下此篇博客,經測試生成500萬條數據後臺用了9秒,徹底能夠接受java




1. Random

random僞隨機數類在 java.util 包下,是最經常使用的隨機數生成器,其使用線性同餘公式來生成隨機數,因此才說是僞隨機。該類的實例是線程安全的,多線程併發使用可能會遇到爭用問題,這時可用 ThreadLocalRandom 來解決這個問題,此外還有 SecureRandom 、SplittableRandom 隨機生成器,這裏就不擴展說明了sql




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


從結果和源碼能夠看出:app

  • 這裏補充一下seed是final類型,線程更安全
  • 給定seed以後,僞隨機數的序列是肯定的
  • 而沒有給seed由於依賴於變化的時間,因此每次的序列是不肯定的
  • 經常使用 new Random.nextInt(int n)來生成僞隨機數




4. Math.random


咱們最經常使用仍是這個函數,靜態調用方便簡單dom

// 底層仍是用了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;
}

從源碼能夠看出:ide

  • 這個類方便咱們使用僞隨機數,每次調用就新建一個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加密
相關文章
相關標籤/搜索