Java 随机数比较和分析

上一篇 / 下一篇  2012-08-03 10:14:56 / 个人分类:Java

51Testing软件测试网2[J%iEg iF L

  概况:

A AWv }051Testing软件测试网#D)HX9h ?7] ?s

  本文概述2种jdk的随机数实现方式,旨在了解其运行机理。并得出运行效率比较。但这2种随机数生成还是会存在一定安全风险(伪随机数有可能会被猜出随机序列),最后还给出另一种相对更安全的随机数产生方式。附录还给出jdk的nextInt(n)函数的代码分析。51Testing软件测试网2]BtPS2y r"X

51Testing软件测试网.I8e.E9ap E

  一、2种产生方式:

FA&vc,\yqt:d] rq3?051Testing软件测试网0VcI#D;^:fR

  一般通过jdk获取0~N(N为自然数)的随机数可以通过下面2种方式获取51Testing软件测试网0V0X/aO4cRmB.\

7wLD?[&g[ R0  1、Math.random() ——返回[0,1)的随机小数,通过(int) (n * Math.random())即可获取[0,n)的随机数51Testing软件测试网 x%I7f:J(lQ tO

P,Z&RCM:Sh0  2、java.util.Random的nextInt(n)方法 ——返回[0,n)的随机小数

5~:_u9l'WkxE-d0

2P8n)hi*Lv:F`b(A0  在jdk1.6实现中,Math.random()实现如下:

g!ky'g8M-w#b0
    public static double random() {51Testing软件测试网#K1ChI$NP
        if (randomNumberGenerator == null) initRNG();   // randomNumberGenerator是Math中持有的Random类单例51Testing软件测试网%XTzgi!S-l
        return randomNumberGenerator.nextDouble();51Testing软件测试网"gtx:\*LPmU%^u
    }

zq.\C'R0  randomNumberGenerator 原来Math.random()还是基于Random类实现了随机数生成。51Testing软件测试网7F{8N8RSX8^V

51Testing软件测试网oU,p]|

  下面再看下Random类的nextDouble()和nextInt(n)分别怎么实现:

,t+]bj9}C0
    public double nextDouble() {
-`*~s{jR-QQ0        return (((long)(next(26)) << 27) + next(27)) 51Testing软件测试网!g{Hr!Lj8F
     / (double)(1L << 53);
U5h8Np\@5B r'pI0    }
51Testing软件测试网KUM6~/Z Y\a

    public int nextInt(int n) {
,Pa*i-zp~r0        if (n <= 0)
6fj z{ \!^(HZ? l-Y;H0            throw new IllegalArgumentException("n must be positive");
51Testing软件测试网u*p\.Z%D(Ze G]

4X9K IQ v!T0        if ((n & -n) == n)  // i.e., n is a power of 251Testing软件测试网8Kc)}Vd4`!D)H8n
            return (int)((n * (long)next(31)) >> 31);
51Testing软件测试网h%O/h,N0T0QmJ'[

51Testing软件测试网1I*d @;GRv+Ok1V`5R

        int bits, val;51Testing软件测试网u8[3E W-Z5Z$W9B
        do {
V8d+H'q0f I0            bits = next(31);
@ d`M:D"C0p \'l0            val = bits % n;51Testing软件测试网:w;{ciZ.N~d,E'` q
        } while (bits - val + (n-1) < 0);   // 这里为什么要这么判断,请见附录a51Testing软件测试网B"n m9BW|
        return val;51Testing软件测试网/O,o \6j7v4Y~!e6jT
    }
51Testing软件测试网)N @8F[+Q8N p(S^

51Testing软件测试网-bp!zZ6r(C|-`3?*[z

  从2段程序中可以看到,最终实现随机数的生成还是依赖于next(n)。

:pJ+Wi K4l#d0

w5F1H N0oM:L_ {0   (注:next(n)的功能是产生n位bits,每个bit位随机为0或1,当n<32时,next(n)产生的随机数范围为0~2^31-1, 当n=32时,产生随机数范围为-2的31次方~2^31-1。源码中的next函数最终返回return (int)(nextseed >>> (48 - bits)) 是因为随机种子是48位,所以最终返回的是随机种子的高n位bit)51Testing软件测试网g @[9lu4P:U

Q%x9\ fG/C;W4_0  而nextDouble()中铁定调用了2次next(n),而nextInt(n)则不确定,但平均调用next(n)的次数在2次以下(见附录b证明)。所以nextInt(n)比nextDouble()获取随机数的效率要高。

SuiKR9b)YvY051Testing软件测试网0`,w*?(kH"J$F0M1dr!F/y-C

  另外,通过(int) (n * Math.random())获取的整数随机数其实是通过double随机数近似而来,准确性相对nextInt(n)来说应该会低些。

R#u m%Nd"S:SJo7Al0

@gL;JO:UY0  二、下面是运行速度比较:

m;Yo*cz#Du];k:XO$vx051Testing软件测试网$x;~ p[,a-h;[*P'k

51Testing软件测试网6^ uD/ticDE.^F;C1B

public class RandomTest{51Testing软件测试网{AL$N5j z1{2Zps9v*\
 static Random random = new Random();
CXT*_ J6d,a7`a9H0 51Testing软件测试网%Oip0b,gh6F:Z:_
    static int r1(int n) {51Testing软件测试网 v;} V IQ1_1i
        return (int)(Math.random() * n);51Testing软件测试网l3w7Jk(W0N;T!Y2G+L)Mf
    }51Testing软件测试网Jz;[|c'J$Ru+R[An
    static int r2(int n) {51Testing软件测试网:W5o)H`-L%b
        return random.nextInt(n);51Testing软件测试网`.\5Z7`M%wD5_
    }51Testing软件测试网Fz%O0c w(j/c4k
 51Testing软件测试网'Tz1W[:Lg4iX
 public static void main(String[] args) {
m/A(cZ?-[(GL0  long t1 = System.nanoTime();
0P `nCV*zKh\9B0  for (int i=0;i<10000;i++) {51Testing软件测试网UH*bu ykcK\C'Oi
   r1(1024);
+rJ3L,c7Xm*yxI0  }51Testing软件测试网 Q6B{ `7h
  long t2 = System.nanoTime();51Testing软件测试网dJ3` [ y2QJ k
  System.out.println(t2 - t1);
2Te`4E'F'[Dz0  t1 = System.nanoTime();51Testing软件测试网*M,D,Pnr*s#n
  for (int i=0;i<10000;i++) {
}N \+PT7k5X.c1{5hc0   r2(1024);
#cs fa8q$r"p/Q }$h0  }
(]|3o.g.~ P#`[0  t2 = System.nanoTime();51Testing软件测试网 A-fG;jn)@Xw
  System.out.println(t2 - t1);51Testing软件测试网!e%Ot]#og
 }
P8h-U y1T,f0}
51Testing软件测试网 [{0\g?Y9V-}2R

  结果:

8[(T4hT)n I7j#j051Testing软件测试网/Q4} m2sJv%b DN

  3422502
L7ZQc/dN0  1335365
51Testing软件测试网3L7zJG2Ja j

c|7O/E4T0  结论:无论从准确性和效率,nextInt(n)都比Math.random()要好。

+@ j*c{6{;_oV:g051Testing软件测试网%^v4BL&J z&{

  三、安全性51Testing软件测试网 gUBMI

51Testing软件测试网%mq7X0KSD'qJ {&~8f

  Random类的next(n)方法依靠确定的seed种子来计算nextseed的值(seed位48位的bit),尽管使用了各种运算,但结果仍然是线性可预测的。

$~ ~bi{\:x051Testing软件测试网4ml/O Bs? i]

51Testing软件测试网1TQwV{Z

    protected int next(int bits) {51Testing软件测试网;hRy t7V
        long oldseed, nextseed;
W[A J3e0        AtomicLong seed = this.seed;
M4ONBZ)C5?} r0        do {51Testing软件测试网 }d:ca3^!H gYH
     ldseed = seed.get();
%N'e8e bC'^s0     nextseed = (oldseed * multiplier + addend) & mask;51Testing软件测试网5y%?\K2D4|D
        } while (!seed.compareAndSet(oldseed, nextseed));
F S A'vL_@V)b0        return (int)(nextseed >>> (48 - bits));51Testing软件测试网A+N"K7~FB4\
    }

1H:u;@r:c9Es0  从程序可以看到,只要seed确定,那么nextseed也就确定,那整个序列都可以被重建出来。故如果对于随机的情景,如果攻击者获取了最初 的种子seed,那么他将可以轻易模拟出随机数,并得到下一个seed。或者他通过穷举seed来获取计算的随机数来匹配程序的随机数。

f"\`-s)\0

4j3nD R.A M#wq*?*I0  所以对安全性有要求的随机数应用情景,可以用java.security.SecureRandom。代替伪随机的Random类。该类继承自Random类,并覆盖了next(n)函数,所以可以利用其提供的强随机的种子算法(SHA1PRNG)来生成随机数。51Testing软件测试网Al^v8Lkm*Mf%O2c

51Testing软件测试网Bt2]#H3b!g5bV;C C

  效率上肯定有损失,大概相差1个数量级。51Testing软件测试网{tmHE

51Testing软件测试网WEyop$e

z,@ g!^V N4F4Tm)w0
    static int r3(int n) {
wzivZ@)T0`0     final int ffset = 123456;  // offset为固定值,避免被猜到种子来源(和密码学中的加salt有点类似)
%T{0\3Vb Y0     long seed = System.currentTimeMillis() + offset;51Testing软件测试网X$~v A'{T|X BP:t
     SecureRandom secureRandom1;51Testing软件测试网8e"]fD6k6e3a)dr
  try {
H7T!J'{5`!y`J0   secureRandom1 = SecureRandom.getInstance("SHA1PRNG");
b,\a"Y~0  
6VP,T{r[0     secureRandom1.setSeed(seed);51Testing软件测试网lxS#uB WX
     return secureRandom1.nextInt();
d.Jp-d$@ xpY0  } catch (NoSuchAlgorithmException e) {51Testing软件测试网*U kS]9`-v#exli
   // TODO Auto-generated catch block51Testing软件测试网2I0E.T X dE
   e.printStackTrace();51Testing软件测试网hpl)t|1j9\I~
  }51Testing软件测试网#Q dL| e/x6s m
  return 0;
k"Ew'Xvi2^0    }

\1O%U5Q&Zo5I1`i `4}$x0
51Testing软件测试网:`}8t%U,C)t,it:?w

  四、附录

e4{${EaOy[ h)N051Testing软件测试网 ZTx3x@m0Iw3WtY

  a、nextInt(n)函数解析51Testing软件测试网I)U7`8N pdIx0PM3f%{

51Testing软件测试网 G C0KM8\ I#@o

  该函数进行的工作是把31位的原始随机范围next(31)的结果映射到[0,n)范围之内。但是,如果不经过特殊处理会出现概率不均匀。考虑 下面这么一个例子,设原有均匀的随机数范围是1~100,当我要产生新的随机数范围为1~30时候,我本来可以用原随机范围产生的数分为三组,但是因为 100不被30整除,所以余下第四组是不匀称的:[1,30), [31, 60), [61, 90), [91,100),所以实际产生的结果中,产生1~10的随机数概率会比11~30的要高。jdk对这种情况的做法是,如果对31bit的随机数映射到 [0,n)的时候,如果next(31)产生的数字是最后那部分,则丢弃重试。所以nextInt(n)的循环部分就是处理这个问题。

Mr~/LH eQ051Testing软件测试网 pXjg#nF

  当n是2的整数次幂时,n铁定能被2^31整除,这时候可以直接映射,进行一次next(31)运算即可。

1ss2_"j E"G h,L2S/z4TQ0

9G$S}8q5i1S4gu0  当n不是2的整数次幂是,那就会出现刚才例子中的不均匀情况。所以就要特殊处理:当bits - val + (n-1) < 0 时,判断为不均匀部分,继续循环重试。那这句判断是什么意思呢。51Testing软件测试网Zd"QB|V

n'n+q3OR&K!Of0  对于 2^31 / n = max ...val ,val是2^31除以n的余数, max是倍数,2^31-val=nMax 也就是获取不大于2^31的能整除n的最大整数。则[nMax,2^31)就是不均匀部分。如果bits落入这个范围可判为丢弃并重试。这里我们可以获 得:nMax < 2^31 < n(Max+1)51Testing软件测试网!u{ NXDN;gf

51Testing软件测试网;v|J|$}EU"q-?

  当调用bits = next(31)时候,获取的是[0,2^31)的一个随机数,51Testing软件测试网l7@4Tn|nk.s

51Testing软件测试网S)dz{Y^7P

  假如bits<nMax,则bits - val一定等于n的某个的倍数(小于Max)中,且bits-val+n-1一定小于nMax,故不需重试,直接返回结果。

:gqz_&_0

-[I E{\Koh-U%w-p0  假如bits>=nMax,则bits-val = nMax, 则bits-val+n-1=nMax+n-1=n(Max+1) - 1 > 2^31 -1 ,等价于若bits>=nMax,则bits-val+n-1 >= 2^31 ,由于int型2^31溢出,所以2^31<0,

mh*kas2jgC9p#o051Testing软件测试网y*nd/r3lHO

  所以用while(bits-val+n-1 < 0) 来判断是否需要重试。51Testing软件测试网:[X)Id&C5d

51Testing软件测试网RMO md V1tlnYV W%b

  b、nextInt(n)的的循环次数为什么平均小于251Testing软件测试网:u&t(LL-]5Q

51Testing软件测试网~3{m&J9Y.o k

  附录a介绍了nextInt(n)的原理,了解到产生不均匀数后需要重试。但这个重试次数是多少呢。考虑最坏情况,如nextInt(n)的 javadoc中所说,当n=2^30+1时,最糟糕,[nMax,2^31)的范围达到最大。要使因为如果n<2^30+1更小,那么一定能找到 newMax使得[nNewMax, 2^31) 的范围更小。51Testing软件测试网/F$urW1E$? ]

51Testing软件测试网|2qNF h fI O~-t

  当n=2^30+1,则不均匀范围[2^30+1, 2^31)和均匀范围[0, 2^30)的数目基本是一样的,所以这个时候有50%的机会要重试。即最坏情况下也只有50%的概率重试。51Testing软件测试网%F~s3L7OFD

51Testing软件测试网d6|$af_6B'\s

  所以总体上来说nextInt(n)的平均调用next(n)次数小于2,这也是为什么比Math.random()要快的原因。51Testing软件测试网*Gz l2h LN)z-Z-D


TAG:

 

评分:0

我来说两句

Open Toolbar