内容主要参考
http://www.cnblogs.com/Fskjb/archive/2009/08/29/1556417.html
http://blog.sina.com.cn/s/blog_93dc666c0101h3gd.html
http://www.cnblogs.com/Ant-soldier/p/5500539.html
三篇博客。
先放一道面试真题
以下关于随机数的描述,正确的是:
A. Matn.random() 可以生成 [ 0 , 1 ] 内的任意小数
B. Random.next( 10 ) 可以生成 [ 0 , 10 ] 内的任意整数
C. new java.util.Random().nextInt( 11 ) 可以生成 [ 0 , 10 ] 内的任意整数
D. new java.util.Math().random() 可以生成 [ 0 , 1 ) 内的任意小数
在Java项目中通常主要是通过Math.random方法和Random类来获得随机数的,下面将从概述,使用技巧,源码分析等方面进行介绍。
一、Random类
1.概述
Random类中实现的随机算法是伪随机,也就是有规则的随机,实际上就是一个数字(seed)经过运算后近似随机数的数字。所以实际上伪随机是完全可以通过运算预测的。Java中的随机数种子若不填写就会使用缺省值,即系统时间。
2、Random对象的构造方法
a、public Random()
该构造方法使用一个和当前系统时间对应的相对时间有关的数字作为种子数,然后使用这个种子数构造Random对象。
b、public Random(long seed)
该构造方法可以通过给定的种子进行创建。
如下代码所示:
public class Client {
public static void main(String[] args) {
Random r = new Random();
Random r1 = new Random(10);
for(int i=1;i<4;i++){
System.out.println("第"+i+"次,r=:"+r.nextInt()+"r1=:"+r1.nextInt());
}
}
}
观察结果发现r1多次运行产生结果相同,r不同
3.Java.util.Random()类的方法
PS:什么是随机数生成器序列?类似一个数组,里面存着生成好的随机数,详见第三部分源码分析
- protected int next(int bits):生成下一个伪随机数,参数bits应该是位数。
- boolean nextBoolean():返回下一个伪随机数,它是取自此随机数生成器序列的均匀分布的boolean值,生成true和false的值几率相等。
- void nextBytes(byte[] bytes):生成随机字节并将其置于用户提供的 byte 数组中。
- double nextDouble():返回下一个伪随机数,它是取自此随机数生成器序列的、在0.0和1.0之间均匀分布的 double值,不包括1.0
- float nextFloat():同上,但类型为Float
- double nextGaussian():返回下一个伪随机数,它是取自此随机数生成器序列的、呈高斯(“正态”)分布的double值,其平均值是0.0标准差是1.0。
- int nextInt():返回下一个伪随机数,它是此随机数生成器的序列中均匀分布的 int 值,该值介于int的区间,也就是-231到231-1之间。
- int nextInt(int n):返回一个伪随机数,它是取自此随机数生成器序列的、该值介于[0,n)的区间,也就是0到n,不包括n。
- long nextLong():返回下一个伪随机数,它是取自此随机数生成器序列的均匀分布的 long 值。
- void setSeed(long seed):使用单个 long 种子设置此随机数生成器的种子。
常用的方法主要是2.4.7.8.10,记住这几个即可,下面介绍随机数的使用技巧。
4.使用示例
Random r = new Random();
a、生成[0,1.0)区间的小数
double d1 = r.nextDouble();
直接使用nextDouble方法获得。
b、生成[0,5.0)区间的小数
double d2 = r.nextDouble() * 5;
因为nextDouble方法生成的数字区间是[0,1.0),将该区间扩大5倍即是要求的区间。
同理,生成[0,d)区间的随机小数,d为任意正的小数,则只需要将nextDouble方法的返回值乘以d即可。
c、生成[1,2.5)区间的小数
double d3 = r.nextDouble() * 1.5 + 1;
生成[1,2.5)区间的随机小数,则只需要首先生成[0,1.5)区间的随机数字,然后将生成的随机数区间加1即可。
同理,生成任意非从0开始的小数区间[d1,d2)范围的随机数字(其中d1不等于0),则只需要首先生成[0,d2-d1)区间的随机数字,然后将生成的随机数字区间加上d1即可。
d、生成任意整数
int n1 = r.nextInt();
直接使用nextInt方法即可。
e、生成[0,10)区间的整数
n2 = Math.abs(r.nextInt() % 10);```
以上两行代码均可生成[0,10)区间的整数。
第一种实现使用Random类中的nextInt(int n)方法直接实现。
第二种实现中,首先调用nextInt()方法生成一个任意的int数字,该数字和10取余以后生成的数字区间为(-10,10),然后再对该区间求绝对值,则得到的区间就是[0,10)了。
同理,生成任意[0,n)区间的随机整数,都可以使用如下代码:
```int n2 = r.nextInt(n);
n2 = Math.abs(r.nextInt() % n);```
#####f、生成[0,10]区间的整数
int n3 = r.nextInt(11);
n3 = Math.abs(r.nextInt() % 11);
相对于整数区间,[0,10]区间和[0,11)区间等价,所以即生成[0,11)区间的整数。
#####g、生成[-3,15)区间的整数
int n4 = r.nextInt(18) - 3;
n4 = Math.abs(r.nextInt() % 18) - 3;
生成非从0开始区间的随机整数,可以参看上面非从0开始的小数区间实现原理的说明。
#####h、几率实现
按照一定的几率实现程序逻辑也是随机处理可以解决的一个问题。下面以一个简单的示例演示如何使用随机数字实现几率的逻辑。
在前面的方法介绍中,nextInt(int n)方法中生成的数字是均匀的,也就是说该区间内部的每个数字生成的几率是相同的。那么如果生成一个[0,100)区间的随机整数,则每个数字生成的几率应该是相同的,而且由于该区间中总计有100个整数,所以每个数字的几率都是1%。按照这个理论,可以实现程序中的几率问题。
示例:随机生成一个整数,该整数以55%的几率生成1,以40%的几率生成2,以5%的几率生成3。实现的代码如下:
int n5 = r.nextInt(100);
int m; //结果数字
if(n5 < 55){ //55个数字的区间,55%的几率
m = 1;
}else if(n5 < 95){//[55,95),40个数字的区间,40%的几率
m = 2;
}else{
m = 3;
}```
因为每个数字的几率都是1%,则任意55个数字的区间的几率就是55%,为了代码方便书写,这里使用[0,55)区间的所有整数,后续的原理一样。
当然,这里的代码可以简化,因为几率都是5%的倍数,所以只要以5%为基础来控制几率即可,下面是简化的代码实现:
int n6 = r.nextInt(20);
int m1;
if(n6 < 11){
m1 = 1;
}else if(n6 < 19){
m1= 2;
}else{
m1 = 3;
}
在程序内部,几率的逻辑就可以按照上面的说明进行实现。
##二、java.lang.Math.Random方法
###1.使用
调用这个Math.Random()函数能够返回带正号的double值,该值大于等于0.0且小于1.0,即取值范围是[0.0,1.0)的左闭右开区间,返回值是一个伪随机选择的数,在该范围内(近似)均匀分布。
例如下面的实验代码
编译通过后运行结果如下图
![](http://upload-images.jianshu.io/upload_images/4767614-e3aab895e94f709f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
观察会发现代码的用一个循环10次循环输出num的取值,均随机分布在[0,3)之间!在使用Math.Random()的时候需要注意的地方时该函数是返回double类型的值,所以在要赋值给其他类型的变量的时候注意需要进行塑形转换。
###2.总结
a.通过阅读Math类的源代码可以发现,Math类中的random方法就是直接调用Random类中的nextDouble方法实现的,两者并无差别。
只是random方法的调用比较简单,所以很多程序员都习惯使用Math类的random方法来生成随机数字。
b.java.util.Random()在调用的时候可以实现和java.Math.Random()一样的功能,而且他具有很多的调用方法,相对来说比较灵活。所以从总体来看,使用java.util.Random()会相对来说比较灵活一些。
C.java.Math.Random()实际是在内部调用java.util.Random()的,它有一个致命的弱点,它和系统时间有关,也就是说相隔时间很短的两个random比如:
double a = Math.random();
double b = Math.random();
即有可能会得到两个一模一样的double。
d。在使用random类时,如果想避免出现随机数字相同的情况,则需要注意,无论项目中需要生成多少个随机数字,都只使用一个Random对象即可。
##三、源码分析
其实我也没有看太懂……先把这个博客上的内容贴过来,等弄懂了再补自己的解释吧……
来自 <http://www.cnblogs.com/Ant-soldier/p/5500539.html>
* @param seed the initial seed
* @see #setSeed(long)
*/
++++++++++++++++++带种子数的构造方法+++++++++++++
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);
}
}
++++++++++++++netInt方法带参数的那个源码++++++++++++
* @since 1.2
*/
public int nextInt(int n) {
if (n <= 0)
throw new IllegalArgumentException("n must be positive");
if ((n & -n) == n) // i.e., n is a power of 2
return (int)((n * (long)next(31)) >> 31);
int bits, val;
do {
bits = next(31);
val = bits % n;
} while (bits - val + (n-1) < 0);
return val;
}
可见Random的种子要求 大于0 的 。。。
+++++++++++++++nextDouble方法实现+++++++++++
public double nextDouble() {
return (((long)(next(26)) << 27) + next(27))
/ (double)(1L << 53);
}
+++++++++++++++nextFloat方法实现+++++++++++++
public float nextFloat() {
return next(24) / ((float)(1 << 24));
}
+++++++++++++++++nextInt方法实现:++++++++++
public int nextInt() {
return next(32);
}
可见所有的随机数产生都和一个叫 next方法有关,这个方法是这样的:
* @since 1.1
*/
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));
}
一般计算机的随机数都是伪随机数,以一个真随机数(种子)作为初始条件,然后用一定的算法不停迭代产生随机数,下面介绍两种方法:
算法1:平方取中法。
1)将种子设为X0,并mod 10000得到4位数
2)将它平方得到一个8位数(不足8位时前面补0)
3)取中间的4位数可得到下一个4位随机数X1
4)重复1-3步,即可产生多个随机数
这个算法的一个主要缺点是最终它会退化成0,不能继续产生随机数。
算法2:线性同余法
1)将种子设为X0,
2)用一个算法X(n+1)=(a*X(n)+b) mod c产生X(n+1)
一般将c取得很大,可产生0到c-1之间的伪随机数
该算法的一个缺点是会出现循环。
下面这个就简单啦。
Math类的源代码,Math类中的random方法就是直接调用Random类中的nextDouble方法实现的。
* @see Random#nextDouble()
*/
public static double random() {
Random rnd = randomNumberGenerator;
if (rnd == null) rnd = initRNG();
return rnd.nextDouble();
}