zoukankan      html  css  js  c++  java
  • Java 随机数

    生成随机数的几种方法:


        Math.random()一随机数


        java.util.Random伪随机数(线性同余法生成)


        java.util.concurrent.ThreadLocalRandom 工具类


        java.security.SecureRandom 真随机数


        Apache Commons-Lang 包中的 RandomStringUtils 类

    一. Math.random()

    Math.random():(产生[0,1)范围的double随机数)

    public static double random() {
            Random rnd = randomNumberGenerator;
            if (rnd == null) rnd = initRNG();
            return rnd.nextDouble();
    }
     
     
    private static Random randomNumberGenerator;//伪随机数生成器
     
    private static synchronized Random initRNG() {
            Random rnd = randomNumberGenerator;
            return (rnd == null) ? (randomNumberGenerator = new Random()) : rnd;
        }

    源码分析:

        当第一次调用Math.random()方法时,会生成伪随机数生成器randomNumberGenerator,之后再调用此方法将不再生成伪随机数生成器,而是继续沿用此伪随机数生成器。此种生成随机数的方式是线程安全的,但是在多线程下可能性能比较低。
        Math.random()实际上内部调用了Random类,所以它也是伪随机数,只是我们无法指定种子

    二. java.util.Random工具类

        Random用来创建伪随机数。所谓伪随机数,是指只要给定一个初始的种子,产生的随机数序列是完全一样的。
        基本算法:linear congruential pseudorandom number generator (LGC) 线性同余法伪随机数生成器
        缺点:可预测
        在注重信息安全的应用中,不要使用 LCG 算法生成随机数,请使用 SecureRandom

    public Random() {
            this(seedUniquifier() ^ System.nanoTime());
        }
     
    public Random(long seed) {
            if (getClass() == Random.class)
                this.seed = new AtomicLong(initialScramble(seed));
            else {
                // subclass might have overriden setSeed
                this.seed = new AtomicLong();
                setSeed(seed);
            }
        }


    源码分析:

        Random类默认使用当前系统时间作为种子,只要种子一样,产生的随机数也一样。种子确定,随机算法也确定,得出的随机数也是确定的。

        要生成一个随机数,可以使用nextInt()、nextLong()、nextFloat()、nextDouble():

    Random r = new Random();
    r.nextInt(); // 2071575453,每次都不一样
    r.nextInt(10); // 5,生成一个[0,10)之间的int
    r.nextLong(); // 8811649292570369305,每次都不一样
    r.nextFloat(); // 0.54335...生成一个[0,1)之间的float
    r.nextDouble(); // 0.3716...生成一个[0,1)之间的double
    
     // new Random() 获取1000000以下整数随机值
    for (int i = 0; i < 100; i++) {
        String code = String.valueOf(new Random().nextInt(1000000));
        System.out.println(code);
    }    


    为什么每次运行程序,生成的随机数都是不同的,看不出来伪随机数的特性?

        这是因为我们创建Random实例时,如果不给定种子,就使用系统当前时间戳作为种子,因此每次运行时,种子不同,得到的伪随机数序列就不同。

    如果我们在创建Random实例时指定一个种子,就会得到完全确定的随机数序列:

     // 指定一个种子,就会得到完全确定的随机数序列 51, 80, 41, 28, 55...
    Random random = new Random(12345);
        for (int i = 0; i < 10; i++) {
        System.out.println(random.nextInt(100));
    }

    三. java.util.concurrent.ThreadLocalRandom 工具类

    ThreadLocalRandom 是JDK 7之后继承至java.util.Random

    源码:

    public static ThreadLocalRandom current() {
            return localRandom.get();
        }
     
    private static final ThreadLocal<ThreadLocalRandom> localRandom =
            new ThreadLocal<ThreadLocalRandom>() {
                protected ThreadLocalRandom initialValue() {
                    return new ThreadLocalRandom();
                }
        };
    
    //ThreadLocalRandom继承于Random
    ThreadLocalRandom() {
            super();   //java.util.Random的构造方法
            initialized = true;
        }


    使用:

    import java.util.concurrent.ThreadLocalRandom;
     
    public class ThreadLocalRandomTest {
        
        public static void main(String[] args) {
            new MyThread().start();
            new MyThread().start();
        }
    }
     
    class MyThread extends Thread{
        public void run(){
            for(int i=0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+":"+ThreadLocalRandom.current().nextDouble());
            }
        }

    源码分析:

        每一个线程有一个独立的随机数生成器,用于并发产生随机数,能够解决多个线程发生的竞争争夺,效率更高。ThreadLocalRandom 不是直接用 new 实例化,而是第一次使用其静态方法 current() 得到ThreadLocal<ThreadLocalRandom>实例,然后调用 java.util.Random 类提供的方法获得各种随机数。

    四. java.Security.SecureRandom(继承至java.util.Random)

    实际上真正的真随机数只能通过量子力学原理来获取,而我们想要的是一个不可预测的安全的随机数,SecureRandom就是用来创建安全的随机数的:

    SecureRandom sr = new SecureRandom();
    System.out.println(sr.nextInt(100));

        SecureRandom无法指定种子,它使用RNG(random number generator)算法。
        JDK的SecureRandom实际上有多种不同的底层实现,有的使用安全随机种子加上伪随机数算法来产生安全的随机数,有的使用真正的随机数生成器。
        实际使用的时候,可以优先获取高强度的安全随机数生成器,如果没有提供,再使用普通等级的安全随机数生成器:

    import java.util.Arrays;
    import java.security.SecureRandom;
    import java.security.NoSuchAlgorithmException;
    public class TestSecureRandom {
        public static void main(String[] args) {
            SecureRandom sr = null;
            try {
                sr = SecureRandom.getInstanceStrong(); // 获取高强度安全随机数生成器
            } catch (NoSuchAlgorithmException e) {
                sr = new SecureRandom(); // 获取普通的安全随机数生成器
            }
            byte[] buffer = new byte[16];
            sr.nextBytes(buffer); // 用安全随机数填充buffer
            System.out.println(Arrays.toString(buffer));
        }
    }

        SecureRandom的安全性是通过操作系统提供的安全的随机种子来生成随机数。这个种子是通过CPU的热噪声、读写磁盘的字节、网络流量等各种随机事件产生的“熵”。

        在密码学中,安全的随机数非常重要。如果使用不安全的伪随机数,所有加密体系都将被攻破。因此,时刻牢记必须使用SecureRandom来产生安全的随机数。

        需要使用安全随机数的时候,必须使用SecureRandom,绝不能使用Random!

    使用:

    //采用SecureRandom 生成6位验证码
    private static String getRandom6() throws NoSuchAlgorithmException {
            
            SecureRandom random= SecureRandom.getInstance("SHA1PRNG");
            int verifiCode = (int)Math.ceil(random.nextFloat()*1000000);
            String verifiCodeStr = String.valueOf(verifiCode);
                    //处理产生的随机数不及6位的情况
            while(verifiCodeStr.length()<6){
                verifiCode = (int)Math.ceil(random.nextFloat()*1000000);
                verifiCodeStr = String.valueOf(verifiCode);
            }
            return verifiCodeStr;
        }

    SecureRandom提供加密的是强随机数生成器,种子是不可预知的,产生的随机数也是不确定。

        从理论上来说计算机产生的随机数都是伪随机数,那么如何产生高强度的随机数?
        答:产生高强度的随机数,有两个重要的因素:种子和算法。

            算法可以有很多种, 如何选择种子是非常关键的因素。
            如Random,它的种子是System.currentTimeMillis().所以它的随机数都是可以预测的。

        那么如何得到一个近似随机的种子?

                可以利用计算机收集的各种信息,如键盘输入时间,cpu时钟,内存使用状态,硬盘空闲空间,IO延时,进程的数量,线程数量等来得到以及近似随机的种子。如此,除了理论上有破解的可能,实际上基本没有被破解的可能。事实表明,现在高随机数的生成都是这样实现的。

    五. Apache Commons-Lang 包中的 RandomStringUtils 类

    RandomStringUtils 类的实现上也是依赖了java.util.Random工具类

    使用

    import org.apache.commons.lang.RandomStringUtils;
     
    public class RandomStringUtilsTest {
        
        public static void main(String[] args) {
            
            //生成64位长度的数字字符串
            String result = RandomStringUtils.random(64,false,true);
            System.out.println("数字random:"+result);
            
            //生成64位的字母字符串
            result=RandomStringUtils.randomAlphabetic(64);
            System.out.println("字母random:"+result);
            
            //生成32位ASCII字符串
            result=RandomStringUtils.randomAscii(32);
            System.out.println("ASCII random:"+result);
            
            //根据指定字符生成32位随机字符串
             result = RandomStringUtils.random(32, 0, 20, true, true, "qw32rfHIJk9iQ8Ud7h0X".toCharArray());
             System.out.println("random = " + result);
                    
        }
     
    }

    结合以上几种方式测试下各自的性能:

         int sum = 1000000;
    
            // Math.random()
            long starTime = System.currentTimeMillis();
            for (int i = 0; i < sum; i++) {
                String code = (Math.random() + "").substring(2, 8);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("Math.random() 时间消耗 = " + (endTime - starTime));
    
            // Math.random valueOf()
            long starTime1 = System.currentTimeMillis();
            for (int i = 0; i < sum; i++) {
                String code = String.valueOf((int) ((Math.random() * 9 + 1) * Math.pow(10, 5)));
            }
            long endTime1 = System.currentTimeMillis();
            System.out.println("Math.random valueOf() 时间消耗 = " + (endTime1 - starTime1));
    
            // SecureRandom
            long starTime2 = System.currentTimeMillis();
            String SYMBOLS = "0123456789"; // 随机验证码范围
            Random RANDOM = new SecureRandom();
            char[] nonceChars = new char[6];
            for (int i = 0; i < 100; i++) {
                for (int index = 0; index < nonceChars.length; ++index) {
                    nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
                }
            }
            long endTime2 = System.currentTimeMillis();
            System.out.println("SecureRandom() 时间消耗 = " + (endTime2 - starTime2));

    Math.random() 时间消耗 = 549
    Math.random valueOf() 时间消耗 = 72
    SecureRandom() 时间消耗 = 1202

  • 相关阅读:
    每天一个linux命令(21):chgrp,chown,chmod
    设计模式之单例模式
    每天一个linux命令(20):find命令之exec
    每天一个linux命令(19):find 命令概览
    每天一个linux命令(18):locate 命令
    每天一个linux命令(17):whereis 命令
    【6折抢】戴尔i7新品Latitude高性能商用本
    Spring Cloud Gateway VS Zuul 比较,怎么选择?
    Zookeeper怎么实现分布式锁?
    数据库怎么分库分表,垂直?水平?
  • 原文地址:https://www.cnblogs.com/hzzjj/p/14791509.html
Copyright © 2011-2022 走看看