C#生成隨機數的三種方法

隨機數的定義爲:產生的全部數字毫無關係.算法

在實際應用中不少地方會用到隨機數,好比須要生成惟一的訂單號.編程

在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*%

相關文章
相關標籤/搜索