.Net 创建随机数常用的方法:
Random rand = new Random();
Console.WriteLine(rand.Next());
Java中创建随机数的方法:
Random rand = new Random(System.currentTimeMillis());
int i = rand.nextInt();
System.out.println(i);
C/C++中创建随机数的方法:
srand((unsigned)time(NULL));
int num = rand();
printf("%d\n",num);
以上随机数的生成原理,基本上是下面这种思路:
比如,我使用线性同余法来自己生成随机数。
class MyRand {
private int seed;
public MyRand(int seed) {this.seed = seed;}
public int Next()
{
int next = (seed * 29 + 37) % 1000; //线性同余法。
seed = next;
return next;
}
}
线性同余法:第n+1个数=(第n个数*29+37) % 1000
可见,当你new一个MyRand对象的时候,要给他一个初始的种子seed,然后,这个随机数生成器就会以seed为起始值,开始按照上述公式进行计算,每调用一次Next都会以上次的结果来计算下一个结果。
所以,如果你要生成很多个随机数,请在for循环里面多次调用Next,而不要在for循环里面多次new MyRand对象。
如果在for循环里面每次都new MyRand对象,由于其种子一样,所以你每次调用Next都是输出第一个seed,
也就是说:相同的种子,第一次调用Next都是一样的。
另外,如果你把new MyRand对象写在for循环外面,虽然本次for循环会生成一堆不相同的数。但是当你第二次运行这个程序,由于跟上次启动程序使用的seed相同,所以还是生成一堆跟上次一样的随机数。
于是,你可以在程序中使用Environment.TickCount或者当前时间作为种子,这样每次启动程序,就会创建尽量不同种子的Random对象。
.Net中,你使用new Random()的无参构造函数进行初始化的时候,微软其实是在构造函数里面调用了带int seed这个有参数构造函数,微软给了一个初始化种子,这个种子就是Environment.TickCount(电脑启动后的时间毫秒数)。
所以,要想让程序在2个人跑的时候,产生不同的2堆随机数。请尽可能的让2个人使用不同的随机数种子。解决办法就是把随机数种子设置为系统启动后的一个时间的值。而且为了保证for循环里不会每一波循环都在循环内new Random对象,所以inew Random()这个过程最好放在for循环的外面。
但是,问题也来了,在高并发的程序中(网站),两个用户很可能在同一个毫秒内访问过来,如果你按照毫秒的时间来作为种子给她们生成随机数,那么这俩用户可能种子是相同的,结果导致随机数相同。
所以,针对这种同一个程序被多个用户访问的情况,为了保证同一时间里的用户都产生不同的随机数,只需要保证每个用户来的时候,不要给他重新new Random对象就行。
比如,设置一个全局的静态Random对象,给所有访问来的用户使用,每个用户过来,只需要Next()调用一下产生下一个随机数就可以了。
但是, Random对象不是线程安全的,当大量并发用户来访问的时候,底层Web服务器很可能开辟多个线程来处理请求,结果多个并发线程来访问Random对象调用其Next的时候,导致使用同一个“上一个结果”来计算随机值,结果还是产生了相同的Next值。
所以,这种方案,需要给Random对象加锁,同一时刻只允许一个线程进入临界区。
============真随机==============
上面的随机数产生方法,是有数学原理的,基于多个随机数,通过推测其种子seed,很有可能推测出下一个随机数。所以,为了保证随机数的随机,所以需要seed的随机,seed要想真随机,可以采用获取大自然的一些信息。或者人在操作电脑的时候随机的鼠标动作等等。
在Linux/Unix下可以使用"/dev/random"这个真随机数发生器,它的数据主来来自于硬件中断信息。
Windows:CryptGenRandom()函数,主要依据当前进程Id、当前线程Id、系统启动后的TickCount、当前时间、QueryPerformanceCounter返回的高性能计数器值、用户名、计算机名、CPU计数器的值等等来计算。
当然.Net下也可以使用RNGCryptoServiceProvider 类(System.Security.Cryptography命名空间下)来生成真随机数