1. 程式人生 > >隨機數 驗證碼問題

隨機數 驗證碼問題

value 大於等於 mat test 關系 ble sqrt 分別是 新版本

一、前言
  在日常工作當中,經常會有需要獲取隨機數、隨機字符的需求,如:生成隨機數驗證碼、生成隨機字符串簽名、生成2個數字之間的隨機數等。這些場景其根本都在於隨機數的生成,本文將對java當中生成隨機數、隨機字符等常見應用場景及獲取方法進行簡單小結。

二、偽隨機、真隨機數簡介  
  計算機很難產生真正意義上的真隨機數,通常我們所說的產生隨機數,都是指偽隨機數。從一定意義上來說,計算機本身幾乎是不可能產生真正意義上的真隨機數的,因為其一定是按照一定的運算規則來獲取隨機數的;當然,偽隨機數的並不是說這個隨機數就是假的,而是指,這個生成的隨機數是按指定規律運算出來的相對隨機的一個數。這些規律就是指各種編程語言中生成隨機數的算法,java當中用的算法之一是線性同余算法

。(具體介紹請參考http://t1174779123.iteye.com/blog/2037719)
  
三、Java本身的生成隨機數相關方法簡介
  Java本身有2個常用的類來生成隨機數,一個是直接使用java.lang.Math類中的Math.random()方法獲取一個[0.0,1.0)之間的一個double類型的隨機數;另一個是通過java.util.Random類,創建一個隨機數發生器,然後再生成隨機數。通過查看源碼可以發現,Math.random()本身其實也是通過java.util.Random類來實現生成隨機數的,只是說,其使用起來更加簡單方便。

//--JDK1.7中,Math.random()部分源碼
private static Random randomNumberGenerator;
private static synchronized Random initRNG() {
     Random rnd = randomNumberGenerator;
     return (rnd == null) ? (randomNumberGenerator = new Random()) : rnd;
}
public static double random() {
        Random rnd = randomNumberGenerator;
        if (rnd == null) rnd = initRNG();
        return rnd.nextDouble();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

1、Math.random()
  Java中的java.lang.Math類包含常見的一些數學公式函數,如:Math.round(n)四舍五入取整,Math.sqrt(n)計算平方根,Math.abs(n)計算絕對值等;而Math.random()函數則是獲取一個[0.0,1.0)之間一個double類型的偽隨機數
  通過Math.random()獲取一個[0.0,1.0)之間的隨機數後,我們就可以通過簡單運算獲取值在[m,n)之間的隨機數了; 如: Math.random()*10就可以獲取一個[0.0,10.0)之間的一個隨機數,Math.random()*10+5就可以獲取一個[5.0,15.0)之間的一個隨機數,然後將獲取的隨機數進行數據類型轉換就可以獲取我們最終所需的隨機數。
  獲取公式

:(條件:0<=m<=n)
  [m,n)之間:(數據類型)(m+Math.random()*(n-m));
  [m,n]之間:(數據類型)(m+Math.random()*(n-m+1));
2、java.util.Random類
  Java中的java.util.Random類可以創建一個隨機數發生器,其構造函數有2個,分別是Random()Random(long seed),前一個是創建不指定種子的隨機數生成器,後一個是創建指定種子的隨機數生成器,然後通過生成器生成隨機數。
  種子,指生成隨機數算法的起始數字,和生成的隨機數的區間沒有任何關系。Random()構造函數其實默認會指定種子,老版本的JDK用的是System.currentTimeMillis()方法獲取當前計算機時間作為種子,而新版本的JDK用的是System.nanoTime()方法獲取當前cpu核心納秒級時間作為種子。(兩者區別請參考http://blog.csdn.net/dliyuedong/article/details/8806868)

//筆者jdk1.7.0.79版本的Random()構造函數源碼
public Random() {
    this(seedUniquifier() ^ System.nanoTime());
}
  • 1
  • 2
  • 3
  • 4

指定種子的話,如果種子值相同,無論執行多少次,其將生成同一隨機數列
例1:

public class TestRandom {
    public static void main(String[] args) {
        Random random1 = new Random(10);
        Random random2 = new Random(10);
        int random1_1 = random1.nextInt(100);
        int random1_2 = random1.nextInt(100);
        int random2_1 = random2.nextInt(100);
        int random2_2 = random2.nextInt(100);

        System.out.println("random1_1->" + random1_1);
        System.out.println("random1_2->" + random1_2);
        System.out.println("random2_1->" + random2_1);
        System.out.println("random2_2->" + random2_2);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

無論執行多少次,其輸出結果均為:

random1_1->13
random1_2->80
random2_1->13
random2_2->80
  • 1
  • 2
  • 3
  • 4

  Random類中有許多生成隨機數的方法,如Math.random()其實就是調用Random類中的nextDouble()方法來獲取隨機數。這裏對其中常用的方法簡單說明一下:

//返回下一個偽隨機數,它是此隨機數生成器的序列中均勻分布的 int 值
public int nextInt();
//返回一個偽隨機數,它是取自此隨機數生成器序列的、在(包括和指定值(不包括)之間均勻分布的int值
public int nextInt(int n);
// 返回下一個偽隨機數,它是取自此隨機數生成器序列的均勻分布的 long 值
public long nextLong();
// 返回下一個偽隨機數,它是取自此隨機數生成器序列的、在0.0和1.0之間均勻分布float值
public float nextFloat();
// 返回下一個偽隨機數,它是取自此隨機數生成器序列的、在0.0和1.0之間均勻分布的
public double nextDouble();
// 返回下一個偽隨機數,它是取自此隨機數生成器序列的均勻分布的boolean值。
public boolean nextBoolean();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

  基本上有以上常用方法,就可以通過簡單運算獲取我們最終所需的隨機數了,運算方法可以參考第1點說明;當然,Random類還有其它獲取隨機數的方法,感興趣的朋友可以去了解一下。
  
四、常見的隨機數使用場景及生成方法
(註意值的閉包區間)
1、獲取指定數值內的隨機數
例2:

//獲取[0,n)之間的一個隨機整數
public static int getRandom(int n) {
    return (int) (Math.random() * n);
}
  • 1
  • 2
  • 3
  • 4

2、獲取2個數字區間內的隨機數
例3:

//獲取[m,n]之間的隨機數(0<=m<=n)
public static int getRandomBetweenNumbers(int m,int n){     
    return (int)(m + Math.random() * (n - m + 1));
}
  • 1
  • 2
  • 3
  • 4

此方法可用於生成隨機驗證碼。
3、獲取指定長度的隨機字符串
  Java本身並沒有生成隨機字符串的方法,但我們可以通過java自帶的隨機數方法運算獲取所需的隨機字符串。
例4:

//獲取指定位數的隨機字符串(包含小寫字母、大寫字母、數字,0<length)
public static String getRandomString(int length) {
    //隨機字符串的隨機字符庫
    String KeyString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    StringBuffer sb = new StringBuffer();
    int len = KeyString.length();
    for (int i = 0; i < length; i++) {
       sb.append(KeyString.charAt((int) Math.round(Math.random() * (len - 1))));
    }
    return sb.toString();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

  在這個例子中,我們先自定義一個字符串庫KeyString ,然後通過Math.random()方法獲取KeyString長度內的一個隨機數,接著再獲取該隨機數對應KeyString中相應位置的一個字符,最後將隨機獲取並組裝好的字符串返回。
  大家可以發現,這個例子獲取的隨機字符串可能包含小寫字母、大寫字母、數字;如果說還需要包含其它字符的話,如%、#、/、* 等特殊字符,只需將相應字符添加到字符串庫KeyString 中去即可;同理,若想生成只包含小寫字母或者只包含數字的字符串,也只需修改字符串庫KeyString 即可。可以說,這幾乎是獲取隨機字符串的一個“萬金油”方法。

4、隨機生成指定概率的數字
  獲取指定值內的隨機數,從理論上來說,每個數字出現概率都是一樣的,但是,我們可以通過一定的運算實現按一定的概率獲取數字。
  如:我們想隨機生成0、1這兩個數字,但是呢,我們希望0出現的概率為70%,1出現的概率為30%;這個時候,我們可以通過用一定的隨機數區間值來分別表示0、1,從而實現按概率獲取隨機數。
例5:

//輸出0或者1;0出現的概率為70%,1出現的概率為30%
public class TestRandom {
    public static void main(String[] args){
        Random random = new Random();
        int n = random.nextInt(100);
        if(n < 70){
            System.out.println("0");
        }else{
            System.out.println("1");
        }
    }   
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

  由以上示例可知,通過一定的運算,我們就可以按一定概率獲取數字,非常簡單的抽獎小遊戲就可以使用這種方式來設置中獎概率。
例6:

//簡單的按概率獲取數字,註意定義數組時的概率總和需為100%
public class TestRandom {
    public static void main(String[] args) {
        //各數字出現的概率分別是:1(10%)、2(20%)、3(30%)、4(40%)
        float[][] array = new float[][]{{1,10},{2,20},{3,30},{4,40}};  
        int n = luckDraw(array,new Random());
        if(1 == n){
            System.out.println("一等獎");
        }else if(2 == n){
            System.out.println("二等獎");
        }else if(3 == n){
            System.out.println("三等獎");
        }else if(4 == n){
            System.out.println("謝謝參與");
        }
    }
    //簡單幸運抽獎
    public static int luckDraw(float[][] array,Random random){
        int n = 10000;  //總值
        int length = array.length;
        int random_num = random.nextInt(n); //隨機數
        for(int i=0;i<length;i++){
            float before_chance = 0;    //當前概率值之前的總概率
            for(int j=0;j<i;j++){
                before_chance = before_chance + array[j][1]; 
            }
            int value = (int) (array[i][1] / 100 * n); //區間
            int up = (int) (before_chance / 100 * n);   //大於等於該值
            int under = up + value;  //小於該值
            if((up <= random_num) && (random_num < under)){
                return (int) array[i][0];
            }
        }
        return 0;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

  需要指出的是,這只是一個非常簡單的小示例,只適用於要求較低的場景。真正嚴格意義上的抽獎有許多要求,如總出獎數量、每個獎的概率隨時間的推移而變化等,有專門的一些算法去實現抽獎中的各種概率問題,感興趣的朋友可以去了解一下。
  
五、總結
1、Math.random()方法和Random類獲取隨機數的實現方法相同,只是相對而言,Math.random()的使用更簡潔便利,而Random類的方法更豐富、使用更加靈活。
2、高並發系統中,即使種子為毫秒級,java.util.Random類獲取的隨機數,還是可能會相同,從而給系統帶來潛在風險。
3、隨機數是相對隨機的,偽隨機數生成效率高,而真隨機數可能需要一定的硬件支持且生成效率低;並沒有說哪個就一定好,凡事都有相對性,我們可以根據自己業務場景所需選擇。

隨機數 驗證碼問題