C#產生不重複的隨機數並生成隨機文件名

本文轉自:http://blog.ciznx.com/post/csharprandomnumberandrandomfilename.aspx算法

在項目中會遇到須要批量生成文件的時候,好比 asp.net 中生成靜態文件、在 Winform 項目中命名臨時文件等,這時咱們考慮使用隨機名稱,也就不可避免地須要使用到 .Net 提供的 System.Random 類。對實際上,計算機不 可能產生徹底隨機的數字,所謂的隨機數發生器都是經過必定的算法對事先選定的隨機種子作複雜的運算,用產生的結果來近似的模擬徹底隨機數,這種隨機數被稱 做僞隨機數。Random 類自身在 Intellisense 中也表述爲「僞隨機數」。僞隨機數是以相同的機率從一組有限的數字中選取的。所選數字並不具備徹底的隨機性,可是從實用的角度而言,其隨機程度已足夠了。
 多線程

僞隨機數的選擇是從隨機種子開始的,因此爲了保證每次獲得的僞隨機數都足夠地「隨機」,隨機種子的選擇就顯得很是重要。若是隨機種子同樣,那麼同一個隨機數發生器產生 的隨機數也會同樣。通常地,咱們使用同系統時間有關的參數做爲隨機種子,若是不給 Random 的構造函數傳遞參數,那 Random 會以當前系統時間由特定算法算得一個種子,這是.net Framework中的隨機數發生器默認採用的方法。 也就是說,若是時間相同(縱使時間永遠不會相同,但在CPU高速運行的狀態下,系統獲取到的時間是有可能相同的。),即便使用不一樣的 Random 實例,因爲初始化它們的種子(系統時間)是相同的,因此Random 對象的這些實例產生的隨機數仍然是相同的。這一點在實驗中獲得證明,實驗代碼以下:併發

using System;  
  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            for (int i = 0; i < 100; i++)  
            {  
                Console.WriteLine(RandomNumber());  
            }  
            Console.WriteLine("Press any key to continue...");  
            Console.ReadLine();  
        }  
  
        static int RandomNumber()  
        {  
            Random rand = new Random();   
            return rand.Next(1000);  
        }  
  
    }  

在控制檯輸出的結果中,咱們能夠看到,接連輸出的結果會相同,而後再變化,而後繼續相同,再變化……即不規律地間歇性地變化。這個問題在 asp.net 程序中尤爲明顯,由於 asp.net 程序自然是多線程的,常常會遇到高併發,因此若是一個生成文件的操做由很高的併發一塊兒執行,就可能因爲生成的文件名相同而形成 IO 錯誤,或者相互覆蓋。因此生成不重複的隨機數(即「隨機」的隨機數)一直是一個受關注的技術話題。
其實 .Net  自己也已經提供了一些類來提供這樣的支持,好比 System.Security.Cryptography.RNGCryptoServiceProvider 及 CryptGenRandom() 等

對於隨機數的重複部分,有幾種方法能夠方便地解決。好比上述代碼,能夠簡單地將 rand 對象的聲明放在類裏,做爲類的靜態字段來對待,而後在 RandomNumber 方法裏調用它,也就是把上述代碼中的 RandomNumber 代碼改成:
static Random rand = new Random();   
static int RandomNumber()  
{  
    return rand.Next(1000);  
}  

這樣就能夠解決問題了,這樣同樣,每次輸出的隨機數都會不相同:由於 rand 對象只用種子初始化了一次。

在日常的開發中,不少人習慣於將一些零散的方法都集中起來放在一個靜態類中(如 Utils),咱們但願諸如此類的方法在一個方法中就搞定,而上述方法中將字段 rand 的聲明放到了方法的外面,這樣破壞了代碼的整潔性,在代碼重用的時候也容易遺漏,因此仍是要考慮把實現這功能的代碼放在同一個方法中,這仍是要面對這個問題:「若是在相同的時間得到不一樣的隨機種子」。咱們知道 Guid 類型的實例是全局不重複的值類型對象,所以使用它能夠方便的做爲獨立的種子,即每次調用方法都使用一個從 Guid 的二進制值轉換獲得的數字做爲種子。示例代碼以下:
 
static int RandomNumber()  
{  
    Guid randSeedGuid = Guid.NewGuid();  
    Random rand = new Random(BitConverter.ToInt32(randSeedGuid.ToByteArray(),0));  
    return rand.Next(1000);  
}  

 

 
若是以爲這個辦法的效率多是個問題的話,也能夠受此啓發,而使用其餘類型的實例的 GetHashCode() 的返回值做爲種子。
此外,在 System.Security.Cryptography 命名空間中提供了  RNGCryptoServiceProvider 類用於提供隨機支持,能夠用它來做爲隨機器的初始化種子能夠用於生成隨機的隨機數(具體的例子,請參照 MSDN 的示例代碼) 對於上述幾種方法,讀者您能夠自行試驗其效率並作對比。 好了,第一個問題解決了,那文件名呢?其實解決了第一個問題,那問題也就解決了一大半了,由於只要在同一時間產生不一樣的隨機數,那即便是多線程也不怕了;不過還有一個問題,文件系統不容許同名的文件,所以除了要保證本次生成的文件名要不相同,還要保證與以前已經存在的文件名不相同。這也不是什麼難題,使用 System.IO.File.Exist() 方法便可驗證是否已存在相同的文件名,使用一個 while 循環來一直獲取隨機文件名,直到不與已經存在的文件名相同爲止。
相關文章
相關標籤/搜索