Java 随机数比较和分析
上一篇 / 下一篇 2012-08-03 10:14:56 / 个人分类:Java
2s2f/Uw0e;[E0 概况:
H)D5tod Om0Q%r-}1q Z(^}Xx\s0 本文概述2种jdk的随机数实现方式,旨在了解其运行机理。并得出运行效率比较。但这2种随机数生成还是会存在一定安全风险(伪随机数有可能会被猜出随机序列),最后还给出另一种相对更安全的随机数产生方式。附录还给出jdk的nextInt(n)函数的代码分析。
Z,F%Mf;Lh7T ep]0K8Ob$r"H0 一、2种产生方式:
9w Km;L Mmh VQ0S$[v*EqG Yl@8J0 一般通过jdk获取0~N(N为自然数)的随机数可以通过下面2种方式获取51Testing软件测试网N&EdCF1Cj
51Testing软件测试网^t!nQQD(V1、Math.random() ——返回[0,1)的随机小数,通过(int) (n * Math.random())即可获取[0,n)的随机数51Testing软件测试网-~y$Io VA
5z{M.rsdM0 2、java.util.Random的nextInt(n)方法 ——返回[0,n)的随机小数
#[$X-@h bb&Px0Y4MQ E8Y9y:F0 在jdk1.6实现中,Math.random()实现如下:51Testing软件测试网4p ^4d6G^?
public static double random() {51Testing软件测试网,n
R,ep-K+y5ng if (randomNumberGenerator == null) initRNG(); // randomNumberGenerator是Math中持有的Random类单例 :Y.n.z8^B5k9|(h[0 return randomNumberGenerator.nextDouble();51Testing软件测试网IB t }3c$Ci } |
randomNumberGenerator 原来Math.random()还是基于Random类实现了随机数生成。
)|u(X.B6O5o*R;D6YR051Testing软件测试网s5x][6fsz下面再看下Random类的nextDouble()和nextInt(n)分别怎么实现:
P(N[ n0b0 public double nextDouble() { O(N Z j*v ]K0 return (((long)(next(26)) << 27) + next(27)) 51Testing软件测试网#vsoR#w$p _Y&FI!x&a / (double)(1L << 53); +r4i:\n1Y(n%g0 } |
51Testing软件测试网~l
q3fi public int nextInt(int n) { %gHXb5F0 if ((n & -n) == n) // i.e., n is a power of 251Testing软件测试网8u,?S
g4B)k )W1r'kmt1}K|5[0 int bits, val;51Testing软件测试网OZ%C#]*s3b0jeo |
q'{6HJqzl`1o0 从2段程序中可以看到,最终实现随机数的生成还是依赖于next(n)。
6N5B8E| ~~L)Y'Wb;U051Testing软件测试网c{!m l jF5c(注: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软件测试网b0N%v"_/b2iMT2J
'Qy;v5[5y0 而nextDouble()中铁定调用了2次next(n),而nextInt(n)则不确定,但平均调用next(n)的次数在2次以下(见附录b证明)。所以nextInt(n)比nextDouble()获取随机数的效率要高。
D:m2c8EG(GS3[?051Testing软件测试网@&@ Hlhl K] H另外,通过(int) (n * Math.random())获取的整数随机数其实是通过double随机数近似而来,准确性相对nextInt(n)来说应该会低些。51Testing软件测试网a1j6_\%C ]#Fg
#YFq _Ct;c(@Z0 二、下面是运行速度比较:
$~U.a A{^@7BR~L051Testing软件测试网KFy/N?XlCoU(|!EC*V)_0public class RandomTest{51Testing软件测试网lP?~5MXSe%t static Random random = new Random(); 3C#u%r Uq,Rc%}0 2MF$Ovjqw^0 static int r1(int n) {51Testing软件测试网Cz'w ^!MlljK5] return (int)(Math.random() * n); 8g-h ]O R2v(pE9\M0 }51Testing软件测试网H#wIKAM P static int r2(int n) { )N p,Nu4UP0 return random.nextInt(n); -iC'{LH%|0 }51Testing软件测试网7g"Z;Y2zYm"y!] 51Testing软件测试网y)\4I$iM5Y4qh public static void main(String[] args) { %Dc\T\(v ~Bc?0 long t1 = System.nanoTime();51Testing软件测试网3aY,DkP%Tz?;F for (int i=0;i<10000;i++) { 8w:]&r\*t@kq)f0 r1(1024);51Testing软件测试网k^vNG*A }51Testing软件测试网.P y(]?@rr3`r.R7D;S0f long t2 = System.nanoTime(); 7J$^u5Ntk5P,Qn8l0 System.out.println(t2 - t1); #h7_S]?f0 t1 = System.nanoTime();51Testing软件测试网.W9c;^bd9m5Z0H6il for (int i=0;i<10000;i++) { 0u;Bxqu)xYF0 r2(1024); WL.bJQ;h|0 }51Testing软件测试网#x"P9vz.HwZ t2 = System.nanoTime(); 0I[,Q3f9f\8M@9N`0 System.out.println(t2 - t1);51Testing软件测试网 ODt)G,@wsU} }51Testing软件测试网 H+Shj:_}'PY`p } |
结果:51Testing软件测试网 qHZ@/e4e3F
51Testing软件测试网;I8XroLX)V 3422502
"[x8SD)B0 1335365
结论:无论从准确性和效率,nextInt(n)都比Math.random()要好。51Testing软件测试网v-w7puOt
'{H@aF l`0 三、安全性51Testing软件测试网a6` DVc,v [Lsk
51Testing软件测试网0Y {9Q ki wq'TRandom类的next(n)方法依靠确定的seed种子来计算nextseed的值(seed位48位的bit),尽管使用了各种运算,但结果仍然是线性可预测的。51Testing软件测试网 ]I2vX(z,L
C}AK'z uX@-E051Testing软件测试网j:?lfF!u,r
protected int next(int bits) {51Testing软件测试网2^#}._UL-c2`l|Cb long oldseed, nextseed; pz$c*h\$c c"h_:`0 AtomicLong seed = this.seed;51Testing软件测试网-t\4aQ2e9e,Q do {51Testing软件测试网K anC'~G ldseed = seed.get();51Testing软件测试网r ae8frZ1nB nextseed = (oldseed * multiplier + addend) & mask; 6Tqm1pF]m Y0 } while (!seed.compareAndSet(oldseed, nextseed));51Testing软件测试网a~9~jK,G3Zh return (int)(nextseed >>> (48 - bits));51Testing软件测试网v2Z g(vOt @ } |
6Sxs7?n'eS0 从程序可以看到,只要seed确定,那么nextseed也就确定,那整个序列都可以被重建出来。故如果对于随机的情景,如果攻击者获取了最初 的种子seed,那么他将可以轻易模拟出随机数,并得到下一个seed。或者他通过穷举seed来获取计算的随机数来匹配程序的随机数。
$CL)`r-r5pLg{0;WF%t/?)H}Ap+f C0 所以对安全性有要求的随机数应用情景,可以用java.security.SecureRandom。代替伪随机的Random类。该类继承自Random类,并覆盖了next(n)函数,所以可以利用其提供的强随机的种子算法(SHA1PRNG)来生成随机数。51Testing软件测试网m4R~(kwR!?5K
.{Klc8MTfQe0 效率上肯定有损失,大概相差1个数量级。
~%jt i!r(v051Testing软件测试网 A$n X&Q5k E?51Testing软件测试网j,l,Q4nR^
static int r3(int n) {51Testing软件测试网HK0lu|$pTv3x final int ffset = 123456; // offset为固定值,避免被猜到种子来源(和密码学中的加salt有点类似)51Testing软件测试网hRj:s;uw*uS+D[ long seed = System.currentTimeMillis() + offset; :Pi H C(P,fX0 SecureRandom secureRandom1;51Testing软件测试网;d1`7[#V F L?"S try {51Testing软件测试网4?'z4^ s#D@ secureRandom1 = SecureRandom.getInstance("SHA1PRNG"); #u2Dag3e%DV7Q"~/u0 51Testing软件测试网\sh_j C2t7rca7~ secureRandom1.setSeed(seed);51Testing软件测试网a8fC F/kL2[b)Wy7h return secureRandom1.nextInt();51Testing软件测试网E)VDGj"C } catch (NoSuchAlgorithmException e) { ^,TF~(pAM0 // TODO Auto-generated catch block51Testing软件测试网:@ {%gg"g't}7U e.printStackTrace(); AZ#]A|fn%n%@0 }51Testing软件测试网A$V/]F|,HBo8C return 0; dqw2}QaN F lhCs0 } |
mocqj$l0 四、附录
k8Q5| G+? X@0UZT051Testing软件测试网2O:rx;d WZM5^a、nextInt(n)函数解析
6vM%wS"a.B0y*S o|051Testing软件测试网0j,nUAb9er该函数进行的工作是把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)的循环部分就是处理这个问题。51Testing软件测试网w3Os%F'Q3H
,IhYux0 当n是2的整数次幂时,n铁定能被2^31整除,这时候可以直接映射,进行一次next(31)运算即可。51Testing软件测试网9hw.Fyp:c/F]
Pr}"D(M&u0 当n不是2的整数次幂是,那就会出现刚才例子中的不均匀情况。所以就要特殊处理:当bits - val + (n-1) < 0 时,判断为不均匀部分,继续循环重试。那这句判断是什么意思呢。51Testing软件测试网4_f T/u:yv&K9L
51Testing软件测试网9Qu!\K3}I0U_对于 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)
@ U+juS'M?051Testing软件测试网Ha6XvqVm当调用bits = next(31)时候,获取的是[0,2^31)的一个随机数,51Testing软件测试网z3g4iV f@El[
7zTe`V0 假如bits<nMax,则bits - val一定等于n的某个的倍数(小于Max)中,且bits-val+n-1一定小于nMax,故不需重试,直接返回结果。51Testing软件测试网r6}4G*jxWEHA
"x#cMX`mM8d0}0 假如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,51Testing软件测试网%l3L Il]vi{
51Testing软件测试网$O$ZQ[ p2j Cf*Yi0b所以用while(bits-val+n-1 < 0) 来判断是否需要重试。51Testing软件测试网v_(LyY j`5QL
%Ql8X!{%XFo+U c0 b、nextInt(n)的的循环次数为什么平均小于251Testing软件测试网2ek'af,{S |
TAj-QZe&MF0 附录a介绍了nextInt(n)的原理,了解到产生不均匀数后需要重试。但这个重试次数是多少呢。考虑最坏情况,如nextInt(n)的 javadoc中所说,当n=2^30+1时,最糟糕,[nMax,2^31)的范围达到最大。要使因为如果n<2^30+1更小,那么一定能找到 newMax使得[nNewMax, 2^31) 的范围更小。
V0@T.XR051Testing软件测试网4L t%|CZ5Lml)V当n=2^30+1,则不均匀范围[2^30+1, 2^31)和均匀范围[0, 2^30)的数目基本是一样的,所以这个时候有50%的机会要重试。即最坏情况下也只有50%的概率重试。
ft#W*}L4hC0Rf:[8Ji0 所以总体上来说nextInt(n)的平均调用next(n)次数小于2,这也是为什么比Math.random()要快的原因。51Testing软件测试网~5^1Q l8G)e m)A2[a-t
TAG:
不要让那些真正对你好的人,慢慢的从你的生活中消失,无论爱情还是友情,都需要用心经营。
我的栏目
标题搜索
日历
|
|||||||||
日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
1 | 2 | 3 | 4 | 5 | 6 | ||||
7 | 8 | 9 | 10 | 11 | 12 | 13 | |||
14 | 15 | 16 | 17 | 18 | 19 | 20 | |||
21 | 22 | 23 | 24 | 25 | 26 | 27 | |||
28 | 29 | 30 |
我的存档
数据统计
- 访问量: 3339953
- 日志数: 1640
- 建立时间: 2011-12-07
- 更新时间: 2019-12-24