在實際開發工作中經常需要用到隨機數。如有些系統中創建用戶后會給用戶一個隨機的初始化密碼。這個密碼由于是隨機的,為此往往只有用戶自己知道。他們獲取了這個隨機密碼之后,需要馬上去系統中更改。這就是利用隨機數的原理。總之隨機數在日常開發工作中經常用到。而不同的開發語言產生隨機數的方法以及技巧各不相同。筆者這里就以Java語言為例,談談隨機數生成的方法以及一些技巧。
一、利用random方法來生成隨機數。
在Java語言中生成隨機數相對來說比較簡單,因為有一個現成的方法可以使用。在Math類中,Java語言提供了一個叫做random的方法。通過這個方法可以讓系統產生隨機數。不過默認情況下,其產生的隨機數范圍比較小,為大于等于0到小于1的double型隨機數。雖然其隨機數產生的范圍比較小,不能夠滿足日常的需求。如日常工作中可能需要產生整數的隨機數。其實,只要對這個方法進行一些靈活的處理,就可以獲取任意范圍的隨機數。
如我們可以先通過random方法生成一個隨機數,然后將結果乘以10。此時產生的隨機數字即為大于等于0小于10的數字。然后再利用Int方法進行轉換(它會去掉小數掉后面的數字,即只獲取整數部分,不是四舍五入)。最后即可獲取一個0到9的整數型隨機數字。其實現方法很簡單,就是對原有的random方法按照如下的格式進行變型:(int)(Math.Random()*10)即可。其實我們還可以對這個方法進行擴展,讓其產生任意范圍內的隨機數。至需要將這個10換成n即可,如改為(int)(Math.Random()*n)。此時應用程序就會產生一個大于等于0小與n之間的隨機數。如將n設置為5,那么其就會產生一個0到5之間的整數型的隨機數。如果將這個寫成一個帶參數的方法,那么只要用戶輸入需要生成隨機數的最大值,就可以讓這個方法來生成制定范圍的隨機數。在Java中定義自己的工具庫
有時候程序員可能需要生成一個指定范圍內的隨機偶數或者奇數。此時是否可以通過這個方法來實現呢?答案是肯定的。如現在程序要需要生成一個1-100范圍內的偶數。此時該如何實現?首先,需要生成一個0到99之內的隨機數(至于這里為什么是99,大家耐心看下去就知道原因了)。要實現這個需求,很簡單吧,只要通過如下語句就可以實現: i=1+(int)(Math.Random()*100)。其中(int)(Math.Random()*99)產生0到99的整數型隨機數。然后再加上1就是產生1到100之間的隨機整數。然后將產生的隨機數賦值給變量i。但是此時其產生的隨機數即有偶數,又有奇數。而現在程序員需要的是一個隨機的偶數。那么我們可以在后面加上一個if判斷語句。將這個隨機數除以2,如果沒有余數的話(或者余數為0)則表明這個隨機數是偶數,直接返回即可。如果其返回的余數不為零,那么就表明其是奇數,我們只要加上1就變為了偶數,返回即可。注意,在上面的隨機數生成中,筆者采用的范圍是0到99,然后再加上1讓其變為1到100的隨機數。最后的結果就是生成1到100之間的隨機偶數。其實,如果要范圍隨機奇數的話,至需要對上面的語句進行稍微的修改即可。Java:改變你我的世界
假設現在用戶想生成一個任意范圍內的奇數或者偶數,能夠實現嗎?假設現在用戶想實現一個m到n之間的任意偶數(其中m
可見雖然random方法其自身產生的隨機數有比較嚴格的范圍限制。但是只要對其進行合理的轉換,程序員仍然可以采用這個方法產生用戶所需要的隨機數據。
二、通過Random類來生成隨機數。
在Java語言中,除了可以通過random 方法來產生隨機數之外,還可以通過一個random類來產生隨機數。程序開發人員可以通過實例化一個Random對象來創建一個隨機數的生成器。如Random i=new Random()。通過這條語句就利用了Random類創建了一個隨機數的生成器。不過以這種方法創建隨機數時,與采用Random方法產生隨機數的機制不同。利用現在這種方式實例化對象時,Java編譯器會以系統當前的時間作為隨機數生成器的種子。由于時間時時刻刻在變化的。若以這個時間作為生成器的種子,就可以保證生成的隨機數真的是隨機的,其生成的隨機數重復率會大大的降低。
利用這種方法其比較方便。如可以利用提供的關鍵字,讓程序返回一個隨機的整數(采用int nextInt(10))等等。不過其返回控制要比Random方法困難一點。如現在需要系統提供一個10到50之間的隨機奇數, 利用這個Random類就無法完成。也就是說,利用這個Random類來生成隨機數,其只能夠控制上限,而不能夠控制下限。換一句話說,其可以指定最大的隨機數范圍,而不能夠指定最小的隨機數范圍。所以,在靈活性上,其比Random方法要稍微差一點。
另外利用這個方法來實現的話,必須先創建一個對象。也就是說利用Randow類來創建對象。這跟Randow方法不同。像上面舉的例子中,Randow方法本身就是一個math類中方法,可以直接調用,省去對象創建的方法。為此筆者建議各位讀者與程序開發人員,最好還是使用Random方法來創建隨機數。只有在生成一些比較特殊的隨機數時采用Random類。如現在需要生成一個概率密度為高斯分布的雙精度值隨機數時,則通過采用Random類的方法來創建隨機數相對來說比較簡單一點。
三、產生隨機的字符。
上面介紹的兩種方法,產生的都是隨機的數值型數據。但是有時候用戶可能還需要產生隨機的字符。其實也可以利用random方法來產生隨機字符。如可以利用代碼生成一個隨機的小寫字符:(char)(‘a'+Math.random()*(‘z'-‘a'+1))。其實這跟生成任意兩個數之間的隨機數類似。通過以上的代碼就可以生成一個范圍之內的任意隨機字符。通過對這個代碼進行適當的修整,還可以生成任意兩個字符之間的隨機字符與任意大寫字符的隨機字符。其轉換的方式跟上面提到的任意范圍之內的隨機數類似。各位讀者若感興趣的話,可以自己進行測試一下。師傅領進門,修行在自身。如果筆者在這里一股腦兒將所有的答案告訴大家,大家的印象不會很深。大家若回去自己動手試試看,反而更容易記住。
筆者在這里給大家一個提示,只需要根據m+(int)(Math.Random()*(n-m))這條語句來調整(char)(‘a'+Math.random()*(‘z'-‘a'+1))這個代碼即可。
四、進階
通過閱讀Math.random()的源碼,或者干脆利用IDE的自動完成功能,開發人員可以很容易發現,java.lang.Math.random()使用一個內部的隨機生成對象 - 一個很強大的對象可以靈活的隨機產生:布爾值、所有數字類型,甚至是高斯分布。例如:
1
|
new java.util.Random().nextInt( 10 ) |
它有一個缺點,就是它是一個對象。它的方法必須是通過一個實例來調用,這意味著必須先調用它的構造函數。如果在內存充足的情況下,像上面的表達式是可以接受的;但內存不足時,就會帶來問題。
一個簡單的解決方案,可以避免每次需要生成一個隨機數時創建一個新實例,那就是使用一個靜態類。猜你可能想到了java.lang.Math,很好,我們就是改良java.lang.Math的初始化。雖然這個工程量低,但你也要做一些簡單的單元測試來確保其不會出錯。
假設程序需要生成一個隨機數來存儲,問題就又來了。比如有時需要操作或保護種子(seed),一個內部數用來存儲狀態和計算下一個隨機數。在這些特殊情況下,共用隨機生成對象是不合適的。
并發
在Java EE多線程應用程序的環境中,隨機生成實例對象仍然可以被存儲在類或其他實現類,作為一個靜態屬性。幸運的是,java.util.Random是線程安全的,所以不存在多個線程調用會破壞種子(seed)的風險。
另一個值得考慮的是多線程java.lang.ThreadLocal的實例。偷懶的做法是通過Java本身API實現單一實例,當然你也可以確保每一個線程都有自己的一個實例對象。
雖然Java沒有提供一個很好的方法來管理java.util.Random的單一實例。但是,期待已久的Java 7提供了一種新的方式來產生隨機數:
1
|
java.util.concurrent.ThreadLocalRandom.current().nextInt( 10 ) |
這個新的API綜合了其他兩種方法的優點:單一實例/靜態訪問,就像Math.random()一樣靈活。ThreadLocalRandom也比其他任何處理高并發的方法要更快。
經驗
Chris Marasti-Georg 指出:
1
|
Math.round(Math.random() * 10 ) |
使分布不平衡,例如:0.0 - 0.499999將四舍五入為0,而0.5至1.499999將四舍五入為1。那么如何使用舊式語法來實現正確的均衡分布,如下:
1
|
Math.floor(Math.random() * 11 ) |
幸運的是,如果我們使用java.util.Random或java.util.concurrent.ThreadLocalRandom就不用擔心上述問題了。
Java實戰項目里面介紹了一些不正確使用java.util.Random API的危害。這個教訓告訴我們不要使用:
1
|
Math.abs(rnd.nextInt())%n |
而使用:
1
|
rnd.nextInt(n) |