只要運氣(qi)足夠好,一刀(dao)也能(neng)999!JAVA隨機數快(kuai)速入門
時間:2018-12-24 來源:華清遠見
“隨(sui)機(ji)(ji)數”的(de)(de)本(ben)質代(dai)表(biao)了人類的(de)(de)不可預知(zhi)性,這(zhe)一特(te)性在編程(cheng)領域(yu)可以(yi)說是必不可少(shao)的(de)(de)。需(xu)要隨(sui)機(ji)(ji)數發(fa)揮關鍵(jian)性作(zuo)用的(de)(de)例子幾乎隨(sui)處可見:游戲中的(de)(de)數值浮動、抽獎系統中的(de)(de)號(hao)碼生成(cheng)、安全領域(yu)的(de)(de)秘鑰生成(cheng)、服務器集群中的(de)(de)負(fu)載均(jun)衡……這(zhe)些或大(da)或小(xiao)、或簡(jian)單或復雜的(de)(de)功能,都需(xu)要基于(yu)隨(sui)機(ji)(ji)數實現。
所以,本文的(de)(de)目的(de)(de)就是要用(yong)最簡單易懂的(de)(de)方式來介紹(shao)如何在Java中使(shi)用(yong)隨機(ji)數。
不過,在開始之前,首先需要明確(que)的(de)(de)(de)(de)一(yi)點是(shi):本文中介(jie)紹的(de)(de)(de)(de)Java自(zi)帶API生(sheng)(sheng)(sheng)成(cheng)(cheng)(cheng)的(de)(de)(de)(de)隨(sui)(sui)(sui)機(ji)(ji)數(shu)(shu)都是(shi)“偽隨(sui)(sui)(sui)機(ji)(ji)數(shu)(shu)”——生(sheng)(sheng)(sheng)成(cheng)(cheng)(cheng)它(ta)(ta)們(men)的(de)(de)(de)(de)生(sheng)(sheng)(sheng)成(cheng)(cheng)(cheng)算(suan)法(fa)(fa)是(shi)一(yi)種(zhong)(zhong)叫(jiao)做“線性同(tong)余(yu)”的(de)(de)(de)(de)算(suan)法(fa)(fa),它(ta)(ta)接(jie)收一(yi)個數(shu)(shu)字作(zuo)為種(zhong)(zhong)子(zi)(zi),根據該種(zhong)(zhong)子(zi)(zi)輸(shu)出(chu)一(yi)個看(kan)似隨(sui)(sui)(sui)機(ji)(ji)的(de)(de)(de)(de)數(shu)(shu)字。這(zhe)種(zhong)(zhong)算(suan)法(fa)(fa)的(de)(de)(de)(de)輸(shu)出(chu)雖然看(kan)似“隨(sui)(sui)(sui)機(ji)(ji)”,不過它(ta)(ta)的(de)(de)(de)(de)結(jie)果其(qi)(qi)(qi)實(shi)是(shi)有(you)周(zhou)期(qi)的(de)(de)(de)(de)(只(zhi)是(shi)周(zhou)期(qi)很(hen)長),而數(shu)(shu)論的(de)(de)(de)(de)知(zhi)識又告訴我們(men):有(you)周(zhou)期(qi)的(de)(de)(de)(de)東西一(yi)定可以預(yu)言。這(zhe)就宣(xuan)判了Java API生(sheng)(sheng)(sheng)成(cheng)(cheng)(cheng)的(de)(de)(de)(de)隨(sui)(sui)(sui)機(ji)(ji)數(shu)(shu)并不是(shi)真(zhen)正“隨(sui)(sui)(sui)機(ji)(ji)”的(de)(de)(de)(de)隨(sui)(sui)(sui)機(ji)(ji)數(shu)(shu),其(qi)(qi)(qi)生(sheng)(sheng)(sheng)成(cheng)(cheng)(cheng)結(jie)果其(qi)(qi)(qi)實(shi)取決于種(zhong)(zhong)子(zi)(zi)的(de)(de)(de)(de)數(shu)(shu)值,換句話(hua)說,使用相同(tong)種(zhong)(zhong)子(zi)(zi)生(sheng)(sheng)(sheng)成(cheng)(cheng)(cheng)的(de)(de)(de)(de)“隨(sui)(sui)(sui)機(ji)(ji)數(shu)(shu)”一(yi)定相同(tong),無論試多少次。
聽(ting)到這里,可(ke)能會有不(bu)少初(chu)學者對其(qi)表(biao)示失望。不(bu)過別急著走,實際上我們只(zhi)需每次(ci)都找一(yi)個不(bu)同的(de)種子,那么也(ye)能夠令其(qi)生(sheng)成的(de)結(jie)果看(kan)起(qi)來(lai)無限接近于(yu)“隨(sui)機”。
那(nei)么如何才能找(zhao)一個“每次(ci)都(dou)不一樣(yang)”的(de)種(zhong)子呢?需要(yao)注意的(de)是,種(zhong)子可(ke)以(yi)是有規律的(de)——這也是隨(sui)機數(shu)生成(cheng)函數(shu)存在的(de)意義(yi)(否(fou)則(ze)還(huan)要(yao)它干嘛)。這樣(yang)的(de)“種(zhong)子”在現(xian)實生活中或者程序中幾乎隨(sui)處(chu)可(ke)見:例如從1997年1月(yue)1日(ri)0時0分0秒(miao)到你現(xian)在看這篇(pian)文章的(de)時間的(de)每一個毫秒(miao)數(shu)——就都(dou)能滿足要(yao)求(它們都(dou)不同,數(shu)量(liang)也足夠,而且(qie)還(huan)能繼續(xu)擴展)。
正因如此,“偽隨(sui)機(ji)(ji)數(shu)”在絕(jue)大多數(shu)開發環(huan)境下都完全能夠(gou)發揮出類似于“真(zhen)隨(sui)機(ji)(ji)數(shu)”的效果(那(nei)么(me)到底能不(bu)能用計算機(ji)(ji)生成(cheng)“真(zhen)隨(sui)機(ji)(ji)數(shu)”呢(ni)?其實也是可以的,不(bu)過(guo)這不(bu)是本(ben)文要討論的內容)。
道理現(xian)在大家都懂了,接(jie)下來(lai)我們(men)就用(yong)一(yi)些喜聞(wen)樂見的(de)例(li)子來(lai)展示如(ru)何在Java中(zhong)使(shi)用(yong)隨機(ji)數。
在Java中,獲取隨(sui)機數的方法非(fei)常簡(jian)單。Java本身提供了兩個(ge)“開箱即用(yong)(yong)”的工(gong)具:一個(ge)是(shi)(shi)專(zhuan)門(men)用(yong)(yong)于(yu)生成隨(sui)機數的工(gong)具類:java.util.Random;另一個(ge)則是(shi)(shi)Math類提供的用(yong)(yong)于(yu)生成隨(sui)機數的方法:Math.random()。
我們先從(cong)第一(yi)個(ge),也是最(zui)容易(yi)理(li)解的說起:Random類。
需要(yao)使用Random類,首先需要(yao)將(jiang)其實例化(hua)。該(gai)類提供了(le)兩種實例化(hua)方(fang)法:Random()和Random(long seed)
示例如下:

第一種(zhong)構造器是最貼心的(de)(de)——它會(hui)自動找一個“盡可(ke)能(neng)與同一個程序中其它使用該(gai)構造方(fang)法生成的(de)(de)Random對象(xiang)所使用的(de)(de)種(zhong)子(zi)不(bu)同的(de)(de)種(zhong)子(zi)”來構造一個新(xin)對象(xiang)。嗯……聽起來可(ke)能(neng)比較拗口,簡單地說就(jiu)是它會(hui)盡可(ke)能(neng)保證(zheng)該(gai)對象(xiang)提供的(de)(de)隨機(ji)數是完全“隨機(ji)”的(de)(de)。
第二種(zhong)構(gou)造器(qi)則允許開(kai)發者使用(yong)指定的(de)(de)種(zhong)子來(lai)生(sheng)成(cheng)(cheng)隨機(ji)數,其類型是(shi)long(它存在的(de)(de)意義(yi)是(shi):如果某(mou)一天您(nin)發明(ming)了一種(zhong)史(shi)無前例完美的(de)(de)種(zhong)子生(sheng)成(cheng)(cheng)算法,能確保生(sheng)成(cheng)(cheng)“真(zhen)隨機(ji)數”,那么這種(zhong)構(gou)造器(qi)就能派上用(yong)場(chang))。
使用Random對象(xiang)來生成一個隨(sui)機整(zheng)形:

第(di)一個方法是生成一個隨機整形,那么(me)它能夠提供2^23種可能性。
第二種則是指定生成(cheng)“0-給(gei)定范圍(wei)”的(de)隨(sui)機(ji)數(shu)。例(li)如(ru)上(shang)圖(tu)的(de)示例(li)中,其返回值(zhi)將是0-99中的(de)某一個數(shu)值(zhi)。
很多情況下,上面提(ti)(ti)到的(de)兩個方法足(zu)以應(ying)對(dui)絕大多數隨機(ji)數生成(cheng)(cheng)需求。不過,Random還提(ti)(ti)供了更多的(de)隨機(ji)數生成(cheng)(cheng)形式:

上圖的示(shi)例(li)中(zhong),從上至(zhi)下依次用于:
1.生成一(yi)個(ge)隨(sui)機整形(前文已經提到了)
2.生成(cheng)一(yi)個(ge)隨機雙精(jing)度浮點(dian)型
3.生(sheng)成一個隨機布爾型
4.生成一個(ge)隨機浮點型
5.生成一(yi)個隨(sui)機(ji)長整形
6.使用隨機(ji)生成(cheng)的byte結果填滿給定的byte數組
可以(yi)看出,都(dou)十分簡單。
編程是(shi)(shi)一門實踐的(de)(de)藝(yi)術(shu)。現在(zai),我們(men)就(jiu)嘗(chang)試使(shi)用上(shang)述技術(shu)做(zuo)一個(ge)很簡單并且有(you)趣的(de)(de)游(you)戲(xi):在(zai)我們(men)的(de)(de)Java世界中(zhong)有(you)一個(ge)“戰(zhan)(zhan)士”角色,它(暫定(ding)為“它”)每次攻(gong)(gong)擊都會(hui)打出(chu)一個(ge)隨機的(de)(de)傷害(hai)數值(這(zhe)就(jiu)像你玩過的(de)(de)大多(duo)數游(you)戲(xi)那樣)。當然(ran),我們(men)的(de)(de)“戰(zhan)(zhan)士”是(shi)(shi)訓練有(you)素的(de)(de),因此其傷害(hai)值一定(ding)是(shi)(shi)在(zai)100以上(shang),不(bu)會(hui)比這(zhe)個(ge)值還低(di)。但(dan)是(shi)(shi)這(zhe)個(ge)“戰(zhan)(zhan)士”的(de)(de)力量也不(bu)是(shi)(shi)無限大,所以它造(zao)(zao)成(cheng)的(de)(de)傷害(hai)數值最大只能(neng)達到200。也就(jiu)是(shi)(shi)說,每一次攻(gong)(gong)擊,這(zhe)位戰(zhan)(zhan)士根據(ju)發揮情(qing)況(kuang)的(de)(de)不(bu)同會(hui)造(zao)(zao)成(cheng)100-200之間的(de)(de)一個(ge)隨機傷害(hai)。
那么,該如(ru)何使用上面提到的隨機數技術來模擬一下該“戰士”攻擊(ji)5次(ci)的效果呢?
首先,我(wo)們將“戰士類”創造出(chu)來:

可(ke)以看出(chu),上圖設計的“戰士”可(ke)以執行(xing)“攻擊”方(fang)法。不(bu)過現在(zai)這個方(fang)法只能返回0——這可(ke)不(bu)符合前(qian)文提(ti)到的要求。因此,我們需要賦予它一定范圍內的“力量(liang)”:

因(yin)(yin)為(wei)最低傷害(hai)值也(ye)要到100,因(yin)(yin)此我們就用100來(lai)做返(fan)回值的“基(ji)底”,在此基(ji)礎上,浮(fu)動范圍(wei)也(ye)是(shi)100,因(yin)(yin)此我們加上一(yi)個0-101(不包括)之(zhi)間的隨(sui)機數作為(wei)最終結果(guo)。
注(zhu)意,這里只(zhi)需使用(yong)一個(ge)Random實例即(ji)可,因(yin)為(wei)每次使用(yong)該實例執行nextInt()方法時均會獲得(de)一個(ge)新的隨機數,這也是(shi)不給定種子時的效果。
OK,設(she)計(ji)工作完成。是(shi)時候讓這個(ge)“戰士”來展(zhan)示一(yi)下自己的實力了:

上面的代碼中,我(wo)們創建了一個“戰士”實例并循環執行(xing)5次攻擊動作,效(xiao)果是否像我(wo)們設計(ji)的那樣呢?

嗯,完(wan)美。雖然這一次(ci)這個(ge)“戰士”可(ke)能(neng)沒(mei)吃飽,攻擊力偏(pian)低了。我(wo)們再試一次(ci):

可(ke)以看出,這次它(ta)的發揮要(yao)比上一(yi)次好(hao)很多。只(zhi)要(yao)運(yun)氣好(hao),次次都(dou)能打出200的逆天傷害也是有可(ke)能的(當(dang)然,概(gai)率學(xue)告訴我們這種(zhong)情況非常少見)。
那(nei)么(me),如果我(wo)們給(gei)定(ding)一個“種(zhong)子(zi)(zi)”,會怎么(me)樣呢(ni)?例如這(zhe)一次我(wo)們使用“1L”作為種(zhong)子(zi)(zi)創建Random。你會發現:此時(shi),這(zhe)個戰士(shi)無(wu)論進(jin)行(xing)多少次測試,每5次的傷害數值均完全(quan)一致,包括順序(xu)(這(zhe)里就(jiu)不(bu)再用圖片(pian)演(yan)示了,可以自行(xing)體驗(yan))。這(zhe)也就(jiu)驗(yan)證了前文(wen)提(ti)到(dao)的“隨機數生成算(suan)法依(yi)賴于種(zhong)子(zi)(zi)”的說法。
那么Random說完了,接(jie)下(xia)來再介紹一下(xia)Math工具類(lei)提(ti)供的(de)random方法:

可(ke)以看出,這一(yi)方法也很(hen)簡單(dan),它(ta)是(shi)一(yi)個(ge)靜態方法,因此(ci)直(zhi)接使用(yong)Math類(lei)進行(xing)調用(yong)即可(ke)。每次調用(yong)它(ta)均會隨機地(di)返回一(yi)個(ge)范(fan)圍(wei)為(wei)0-1(不包括)的double型數值(它(ta)無法指定(ding)種(zhong)子,因此(ci)使用(yong)起來更加簡單(dan))。
我相信,有些(xie)初學者在第一次(ci)看(kan)到它會覺得(de)很奇怪(guai)(guai):為何這(zhe)個方(fang)法要返回一個如此(ci)怪(guai)(guai)異的結果?
實際上(shang),你能通過這個值(zhi)獲得任(ren)意范圍的(de)隨機數。不(bu)信?我們接下(xia)來繼(ji)續上(shang)面的(de)例子:
現在(zai),我(wo)們的(de)“戰士(shi)”經過(guo)一段時間的(de)刻苦努力,終(zhong)于從(cong)1級的(de)新手成長(chang)(chang)為99級的(de)老手,傷害(hai)的(de)可(ke)能值也成長(chang)(chang)為現在(zai)的(de)100-999(下限(xian)沒變,上(shang)限(xian)提(ti)高了一大截)。
那么,我們(men)該(gai)如何用Math提供的random方(fang)法來實(shi)現(xian)這一效果呢?
首先,我們依(yi)舊是(shi)采用100作為返(fan)回值(zhi)的(de)(de)“基底(di)”,那么(me)浮(fu)動數(shu)值(zhi)的(de)(de)取值(zhi)范(fan)圍(wei)就是(shi)0-899(包括)之間。可是(shi)前文中(zhong)已經提(ti)(ti)到了,Math提(ti)(ti)供(gong)的(de)(de)random方法(fa)只(zhi)能返(fan)回一個隨機的(de)(de)、0-1之間的(de)(de)double數(shu)值(zhi)。這該怎么(me)辦(ban)?
先舉(ju)個(ge)簡單的(de)例(li)子(zi),假如(ru)我要(yao)生成5-7(不包括)之間的(de)隨機(ji)數(也就是只有(you)5,6兩種取值),那么(me)可(ke)以這樣獲取:
返回值=5+(int)((7-5)*Math.random())
例如,某(mou)一次random返回了0.2,那么(me)7-5=2,2*0.2=0.4,轉(zhuan)換為int類型后變為0,最終返回值5+0=5。嗯,這(zhe)個值確實是5-7之(zhi)間的一個隨機數。
再(zai)例(li)如,某(mou)一次random反悔了0.8,那么7-5=2,2*0.8=1.6,轉(zhuan)換為int后還(huan)是1,此時返回值5+1=6,依(yi)舊滿足條件。
綜(zong)上,我們可以看出,使(shi)用(yong)0-1之(zhi)間的小數產生M到N之(zhi)間的隨機數,可以根據以下公(gong)式獲得(de):
結果(guo)=M+(int)((M-N)*Math.random())
這樣一(yi)來,我們的(de)戰(zhan)士也(ye)就可以整裝待(dai)發了(le)(之所以乘以900不是(shi)(shi)乘以899,是(shi)(shi)因為產生的(de)隨(sui)機數無(wu)限(xian)接近但不包括最(zui)大值(zhi),因此我們要加上1):

如上。現在(zai),我們再讓(rang)它來展示一(yi)下自(zi)己的實力:

效果如何呢?如下所(suo)示:

當然,和之前一(yi)樣,只要運氣足夠(gou)好,終有(you)一(yi)天它能打出一(yi)刀999的傷害(hai)。
在(zai)此(ci)基(ji)礎(chu)上,還能添加暴擊效果,使其有一定幾(ji)率將傷(shang)害數值乘以2,或者添加miss效果,使其有一定幾(ji)率傷(shang)害為0……由此(ci)可見,按照(zhao)這(zhe)個原(yuan)理繼續擴展(zhan)的話,終有一天你也能寫(xie)出(chu)一套(tao)“只需體(ti)驗(yan)3分鐘”就(jiu)能讓用戶愛上的款游戲作品了(le)!
當然,那個目標可能并不(bu)容易實現(xian)。但是至少(shao)現(xian)在你(ni)應該已經掌握(wo)了在Java中(zhong)方使(shi)用隨機數(shu)的方法了,這(zhe)也(ye)是本(ben)文的意義所在!
感謝閱讀。

