偽隨機(preundorandom):通過算法產生的隨機數都是偽隨機!!
只有通過真實的隨機事件產生的隨機數才是真隨機!!比如,通過機器的硬件噪聲產生隨機數、通過大氣噪聲產生隨機數
Random生成的隨機數都是偽隨機數!!!
是由可確定的函數(常用線性同余),通過一個種子(常用時鍾),產生的偽隨機數。這意味着:如果知道了種子,或者已經產生的隨機數,都可能獲得接下來隨機數序列的信息(可預測性)
Random類擁有兩個構造方法,用於實現隨機數生成器:
Random( ) | 構造一個隨機數生成器 |
Random(long seed) | 用種子seed構造一個隨機數生成器 |
一、無參構造方法(不設置種子)
雖然表面上看我們未設置種子,但Random構造方法里有一套自己的種子生成機制,源碼如下:
1 /** 2 * Creates a new random number generator. This constructor sets 3 * the seed of the random number generator to a value very likely 4 * to be distinct from any other invocation of this constructor. 5 */ 6 public Random() { 7 this(seedUniquifier() ^ System.nanoTime()); 8 } 9 10 private static long seedUniquifier() { 11 // L'Ecuyer, "Tables of Linear Congruential Generators of 12 // Different Sizes and Good Lattice Structure", 1999 13 for (;;) { 14 long current = seedUniquifier.get(); 15 long next = current * 181783497276652981L; 16 if (seedUniquifier.compareAndSet(current, next)) 17 return next; 18 } 19 } 20 21 private static final AtomicLong seedUniquifier 22 = new AtomicLong(8682522807148012L);
生成種子過程:(參考解密隨機數生成器(二)——從java源碼看線性同余算法)
1、獲得一個長整形數作為“初始種子”(系統默認的是8682522807148012L)
2、不斷與一個變態的數——181783497276652981L相乘(天知道這些數是不是工程師隨便滾鍵盤滾出來的-.-)得到一個不能預測的值,直到 能把這個不能事先預期的值 賦給Random對象的靜態常量seedUniquifier 。因為多線程環境下賦值操作可能失敗,就for(;;)來保證一定要賦值成功
3、與系統隨機出來的nanotime值作異或運算,得到最終的種子
nanotime算是一個隨機性比較強的參數,用於描述代碼的執行時間。源碼中關於nanotime的描述(部分):
/** * Returns the current value of the running Java Virtual Machine's * high-resolution time source, in nanoseconds. * * <p>This method can only be used to measure elapsed time and is * not related to any other notion of system or wall-clock time.
二、有參構造方法(設置種子)
語法:Random ran = Random(long seed)
有參構造方法的源碼如下:
1 /** 2 * Creates a new random number generator using a single {@code long} seed. 3 * The seed is the initial value of the internal state of the pseudorandom 4 * number generator which is maintained by method {@link #next}. 5 * 6 * <p>The invocation {@code new Random(seed)} is equivalent to: 7 * <pre> {@code 8 * Random rnd = new Random(); 9 * rnd.setSeed(seed);}</pre> 10 * 11 * @param seed the initial seed 12 * @see #setSeed(long) 13 */ 14 public Random(long seed) { 15 if (getClass() == Random.class) 16 this.seed = new AtomicLong(initialScramble(seed)); 17 else { 18 // subclass might have overriden setSeed 19 this.seed = new AtomicLong(); 20 setSeed(seed); 21 } 22 } 23 24 private static long initialScramble(long seed) { 25 return (seed ^ multiplier) & mask; 26 }
其中的multiplier和mask都是定值:
1 private static final long multiplier = 0x5DEECE66DL; 2 3 private static final long mask = (1L << 48) - 1;
三、代碼測試
分別采用有參和無參兩種方法,生成[0, 100)內的隨機整數,各生成五組,每組十個隨機數:
1 import java.util.Random; 2 3 public class RandomTest { 4 public static void main(String[] args) { 5 RandomTest rt = new RandomTest(); 6 rt.testRandom(); 7 } 8 9 public void testRandom(){ 10 System.out.println("Random不設置種子:"); 11 for (int i = 0; i < 5; i++) { 12 Random random = new Random(); 13 for (int j = 0; j < 10; j++) { 14 System.out.print(" " + random.nextInt(100) + ", "); 15 } 16 System.out.println(""); 17 } 18 19 System.out.println(""); 20 21 System.out.println("Random設置種子:"); 22 for (int i = 0; i < 5; i++) { 23 Random random = new Random(); 24 random.setSeed(100); 25 for (int j = 0; j < 10; j++) { 26 System.out.print(" " + random.nextInt(100) + ", "); 27 } 28 System.out.println(""); 29 } 30 } 31 }
運行結果如下:
結論:
雖然二者都是偽隨機,但是,無參數構造方法(不設置種子)具有更強的隨機性,能夠滿足一般統計上的隨機數要求。使用有參的構造方法(設置種子)無論你生成多少次,每次生成的隨機序列都相同,名副其實的偽隨機!!