這邊文章是接着 滅霸腳本怎麼隨機刪除服務器的一半文件? 寫的,Linux 命令 shuf(一個使輸出隨機的命令) 的資料好少,我經過 Java 的 Random 類擴展。java
從源碼入手,過程當中遇到不懂的擴展出去,解決完了再回到源碼,直到把核心代碼理解完。算法
/** * An instance of this class is used to generate a stream of * pseudorandom numbers. The class uses a 48-bit seed, which is * modified using a linear congruential formula. (See Donald Knuth, * <i>The Art of Computer Programming, Volume 2</i>, Section 3.2.1.) * <p> * If two instances of {@code Random} are created with the same * seed, and the same sequence of method calls is made for each, they * will generate and return identical sequences of numbers. In order to * guarantee this property, particular algorithms are specified for the * class {@code Random}. Java implementations must use all the algorithms * shown here for the class {@code Random}, for the sake of absolute * portability of Java code. However, subclasses of class {@code Random} * are permitted to use other algorithms, so long as they adhere to the * general contracts for all the methods. * <p> * The algorithms implemented by class {@code Random} use a * {@code protected} utility method that on each invocation can supply * up to 32 pseudorandomly generated bits. * <p> * Many applications will find the method {@link Math#random} simpler to use. * * <p>Instances of {@code java.util.Random} are threadsafe. * However, the concurrent use of the same {@code java.util.Random} * instance across threads may encounter contention and consequent * poor performance. Consider instead using * {@link java.util.concurrent.ThreadLocalRandom} in multithreaded * designs. * * <p>Instances of {@code java.util.Random} are not cryptographically * secure. Consider instead using {@link java.security.SecureRandom} to * get a cryptographically secure pseudo-random number generator for use * by security-sensitive applications. * * @author Frank Yellin * @since 1.0 */
複製代碼
大體翻一下安全
重點:服務器
此實例用於生成僞隨機數,使用48位種子,改自線性同餘方程。app
特色:dom
java.util.concurrent.ThreadLocalRandom
。java.security.SecureRandom
提供安全加密的僞隨機數生成器。僞隨機數算法的核心。至於爲毛用這個算法,怎麼保證隨機性等等更深的問題,我想過,但特麼太複雜了。。。先搞懂這個算法再說吧。。。那些更深的問題,我八成是放棄了,畢竟我是個 Java 開發。(手動狗頭)ide
a*x≡b(mod m)
函數
這個公式的意思是:a*x 和 b 除以 m後餘數相同,讀做 a*x 與 b 同餘,mod 爲 c。post
X(n+1) = (a * X(n) + c) % m
性能
其中 X(0)
爲種子,X(0)
的值算出 X(1)
,X(1)
的值算出 X(2)
...
依次類推,一旦種子(X(0)
)肯定,隨機數隊列即肯定的,也是特色 1 的緣由。
看哈 Random 類怎麼實現的 X(0)
的,有兩個構造器,一個傳種子,一個不傳。
// 不傳參的構造器
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
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;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
private static final AtomicLong seedUniquifier
= new AtomicLong(8682522807148012L);
// 傳參的構造器
public Random(long seed) {
if (getClass() == Random.class)
this.seed = new AtomicLong(initialScramble(seed));
else {
// subclass might have overriden setSeed
this.seed = new AtomicLong();
setSeed(seed);
}
}
複製代碼
無參構造器獲取種子的方法是,用 current(8682522807148012L)
乘以 181783497276652981L
。
compareAndSet
方法保證線程安全,若是檢測到 current
值被其餘線程修改了就再乘一次,for (;;)
至關於 while(ture)
。
接着再用結果 next
異或 System.nanoTime()
,最後獲得的結果就是 seed
。
System.nanoTime()
有點像 System.currentTimeMillis()
,是一個跟時間有關的隨機數,但他只能用來計算時間段,好比方法體先後分別調用 System.nanoTime()
再相減能夠計算方法執行時間,但不能用於計算當前日期。
線性同餘出如今 next
方法中。這是 Random
類的核心,全部計算隨機數的方法都調用次方法。
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));
}
private static final long multiplier = 0x5DEECE66DL;
private static final long addend = 0xBL;
private static final long mask = (1L << 48) - 1;
複製代碼
核心代碼 nextseed = (oldseed * multiplier + addend) & mask
跟 X(n+1) = (a * X(n) + c) % m
是等價的。
multiplier、addend、mask
分別對應 a、c、m
,% m
變成了 & mask
。
網上這麼解釋的:
因此 x & [(1L << 48)–1]
與 x(mod 2^48)
等價。
why 48 bit seed in util Random class?
You need more bits of state than bits of output, because the nature of an LCG is such that the low-order bits of state are not very random at all. So if you want 32-bit outputs, you need more than 32 bits of state.
Why use 48 rather than 64? Because 48 is enough, and you're designing this decades ago, so there are good reasons to want to avoid using any more resources than are strictly necessary.
好了好了,求放過,Java位運算符我知其然不知其因此然,還有爲毛線性同餘方程能夠用來作隨機數算法我也不知道,不想僞裝知道僞裝高深(手動笑哭),但願懂的大佬們帶帶我。。。(手動狗頭),哈哈哈哈。
每一次成長,都想與你分享。