随机数的定义为:产生的全部数字毫无关系.算法
在实际应用中不少地方会用到随机数,好比须要生成惟一的订单号.编程
在C#中获取随机数有三种方法:windows
一.Random 类api
Random类默认的无参构造函数能够根据当前系统时钟为种子,进行一系列算法得出要求范围内的伪随机数.并发
1
2
|
Random rd =
new
Random();
int
i = rd.Next();
|
这种随机数能够达到一些要求较低的目标,可是若是在高并发的状况下,Random类所取到的系统时钟种子接近甚至彻底同样,就颇有可能出现重复,这里用循环来举例dom
1
2
3
4
5
|
for
(
int
i = 0; i < 10; i++)
{
Random rd =
new
Random();
//无参即为使用系统时钟为种子
Console.WriteLine(rd.Next().ToString());
}
|
这个例子会输出10个相同的"随机数".编程语言
突显出的问题:由于Random进行伪随机数的算法是固定的,因此根据同一个种子计算出的数字必然是同样的.而以当代计算机的运行速度,该循环几乎是在瞬间完成的,种子一致,因此会出现10次循环输出同一随机数的状况.ide
有的时候使用random生成随机数的时候每每不是随机的 这是为何呢?函数
随机数生成方法能够说是任何编程语言必备的功能,它的重要性不言而言,在C#中咱们一般使用Random类生成随机数,在一些场景下,我却发现Random生成的随机数并不可靠,在下面的例子中咱们经过循环随机生成5个随机数:高并发
for (int i = 0; i < 5; i++) { Random random = new Random(); Console.WriteLine(random.Next()); }
这段代码执行后的结果以下所示:
2140400647 2140400647 2140400647 2140400647 2140400647
经过以上结果可知,随机数类生成了5个相同的数,这并不是咱们的预期,为何呢?为了弄清楚这个问题,零度剖析了微软官方的开源Random类,发如今C#中生成随机数使用的算法是线性同余法,经百科而知,这种算法生成的不是绝对随机,而是一种伪随机数,线性同余法算法的的公式是:
第N+1个数 = ( 第N个数 * A + B) % M
上面的公式中A、B和M分别为常数,是生成随机数的因子,若是以前从未经过同一个Random对象生成过随机数(也就是调用过Next方法),那么第N个随机数为将被指定为一个默认的常数,这个常数在建立一个Random类时被默认值指定,Random也提供一个构造函数容许开发者使用本身的随机数因子,这一切可经过微软官方开源代码看到:
public Random() : this(Environment.TickCount) { } public Random(int Seed) { }
经过默认构造函数建立Random类时,一个Environment.TickCount对象做为因子被默认传递给第二个构造函数,Environment.TickCount表示操做系统启动后通过的毫秒数,计算机的运算运算速度远比毫秒要快得多,这致使一个的具备毫秒精度的因子参与随机数的生成过程,但在5次循环中,咱们使用了同一个毫秒级的因子,从而生成相同的随机数,另外,第N+1个数的生成与第N个数有着直接的关系。
在上面的例子中,假设系统启动以来的毫秒数为888毫秒,执行5次循环用时只有0.1毫秒,这致使在循环中建立的5个Random对象都使用了相同的888因子,每次被建立的随机对象又使用了相同的第N个数(默认为常数),经过这样的假设咱们不难看出,上面的结果是必然的。
如今咱们改变这个格局,在循环以外建立一个Random对象,在每次循环中引用它,并经过它生成随机数,并在同一个对象上屡次调用Next方法,从而不断变化第N个数,代码以下所示:
Random random = new Random(); for (int i = 0; i < 5; i++) { Console.WriteLine(random.Next()); }
执行后的结果以下所示:
391098894 1791722821 1488616582 1970032058 201874423
咱们看到这个结果确实证明了咱们上面的推断,第1次循环时公式中的第N个数为默认常数;当第二次循环时,第N个数为391098894,随后不断变化的第N个数做为因子参与计算,这保证告终果的随机性。
虽然经过咱们的随机数看起来也很随机了,但一定这个算法是伪随机数,当第N个数和因子都相同时,生成的随机数仍然是重复的随机数,因为Random提供一个带参的构造函数容许咱们传入一个因子,若是传入的因子随机性强的话,那么生成的随机数也会比较可靠,为了提供一个可靠点的因子,咱们一般使用GUID产生填充因子,一样放在循环中测试:
for (int i = 0; i < 5; i++) { byte[] buffer = Guid.NewGuid().ToByteArray(); int iSeed = BitConverter.ToInt32(buffer, 0); Random random = new Random(iSeed); Console.WriteLine(random.Next()); }
这样的方式保证了填充因子的随机性,因此生成的随机数也比较可靠,运行结果以下所示:
734397360 1712793171 1984332878 819811856 1015979983
在一些场景下这样的随机数并不可靠,为了生成更加可靠的随机数,微软在System.Security.Cryptography命名空间下提供一个名为RNGCryptoServiceProvider的类,它采用系统当前的硬件信息、进程信息、线程信息、系统启动时间和当前精确时间做为填充因子,经过更好的算法生成高质量的随机数,它的使用方法以下所示:
byte[] randomBytes = new byte[4]; RNGCryptoServiceProvider rngServiceProvider = new RNGCryptoServiceProvider(); rngServiceProvider.GetBytes(randomBytes); Int32 result = BitConverter.ToInt32(randomBytes, 0);
经过这种算法生成的随机数,通过成千上万次的测试,并未发现重复,质量的确比Random高了不少。另外windows api也提供了一个非托管的随机数生成函数CryptGenRandom,CryptGenRandom与RNGCryptoServiceProvider的原理相似,采用C++编写,若是要在.NET中使用,须要进行简单的封装。它的原型以下所示:
BOOL WINAPI CryptGenRandom( _In_ HCRYPTPROV hProv, _In_ DWORD dwLen, _Inout_ BYTE *pbBuffer );
以上就是零度为您带来的随机数生成方法和基本原理,您能够经过需求和场景选择最佳的方式,Random算法简单,性能较高,适用于随机性要求不高的状况,因为RNGCryptoServiceProvider在生成期间须要查询上面提到的几种系统因子,因此性能稍弱于Random类,但随机数质量高,可靠性更好。
二.Guid 类
System.Guid
GUID (Globally Unique Identifier) 全球惟一标识符
GUID的计算使用到了不少在本机可取到的数字,如硬件的ID码,当前时间等.所计算出的128位整数(16字节)能够接近惟一的输出.
1
|
Console.WriteLine(Guid.NewGuid().ToString());
|
计算结果是xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx结构的16进制数字.固然这个格式也是能够更改的.
三.RNGCryptoServiceProvider 类
System.Security.Cryptography.RNGCryptoServiceProvider
RNGCryptoServiceProvider 使用加密服务提供程序 (CSP) 提供的实现来实现加密随机数生成器 (RNG)
1
2
3
4
|
RNGCryptoServiceProvider csp =
new
RNGCryptoServiceProvider();
byte
[] byteCsp =
new
byte
[10];
csp.GetBytes(byteCsp);
Console.WriteLine(BitConverter.ToString(byteCsp));
|
因该类使用更严密的算法.因此即便以下放在循环中,所计算出的随机数也是不一样的.
1
2
3
4
5
6
7
|
for
(
int
i = 0; i < 10; i++)
{
RNGCryptoServiceProvider csp =
new
RNGCryptoServiceProvider();
byte
[] byteCsp =
new
byte
[10];
csp.GetBytes(byteCsp);
Console.WriteLine(BitConverter.ToString(byteCsp));
}
|
1
|
可是RNGCryptoServiceProvider的计算较为繁琐,在循环中使用会消耗形成大量的系统资源开销,使用时需注意.
|
Membership.GeneratePassword()
Membership是一个方便快捷的进行角色权限管理的类,偶然发现一个颇有意思的方法,没研究过是如何实现的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
static
string
GeneratePassword(
int
length,
int
numberOfNonAlphanumericCharacters);
//
// 摘要:
// 生成指定长度的随机密码。
//
// 参数:
// numberOfNonAlphanumericCharacters:
// 生成的密码中的标点字符数。
//
// length:
// 生成的密码的字符数。长度必须介于 1 和 128 个字符之间。
//
// 返回结果:
// 指定长度的随机密码。
|
例:
1
2
3
4
|
for
(
int
i = 0; i < 10; i++)
{
Response.Write(Membership.GeneratePassword(20, 1) +
"<br>"
);
}
|
结果为
C!&^HoTNv3!ZHkK9BAbu
azLgER)JJ-UW8q*14yz*
I3qnb]Zxu16ht!kKZ!Q*
9U:MAQ&c1x)^aed@xe**
oL(%4JvfbP&t5*Hpl4l-
6@zj$CnhW&D+|xOf:qIk
A/!Di&l*tY$QaMH0gyzY
z^wu6{1BMq7D^+WU]>f$
1OgIJS3&09fw0F9.|aXA
8F+Gy+L{O6x{SfugME*%