Java实现高效随机数算法的示例代码

前言

事情起源于一位网友分享了一个有趣的面试题:

生成由六位数字组成的ID,要求随机数字,不排重,不可自增,且数字不重复。ID总数为几十万。

初次解答

我一开始想到的办法是

  • 生成一个足够大的ID池(其实就是需要多少就生成多少)
  • 对ID池中的数字进行随机排序
  • 依次消费ID池中的数字

可惜这个方法十分浪费空间,且性能很差。

初遇梅森旋转算法

后面咨询了网友后得知了一个高效的随机数算法:梅森旋转(Mersenne Twister/MT)。通过搜索资料得知:

梅森旋转算法(Mersenne twister)是一个伪随机数发生算法。由松本真和西村拓士在1997年开发,基于有限二进制字段上的矩阵线性递归。可以快速产生高质量的伪随机数,修正了古典随机数发生算法的很多缺陷。
最为广泛使用Mersenne Twister的一种变体是MT19937,可以产生32位整数序列。

PS:此算法依然无法完美解决面试题,但是也算学到了新知识

MT19937算法实现

后面通过Google,找到了一个高效的MT19937的Java版本代码。原代码链接为http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/JAVA/MTRandom.java

import java.util.Random;

/**
 * MT19937的Java实现
 */
public class MTRandom extends Random {

  // Constants used in the original C implementation
  private final static int UPPER_MASK = 0x80000000;
  private final static int LOWER_MASK = 0x7fffffff;

  private final static int N = 624;
  private final static int M = 397;
  private final static int MAGIC[] = { 0x0, 0x9908b0df };
  private final static int MAGIC_FACTOR1 = 1812433253;
  private final static int MAGIC_FACTOR2 = 1664525;
  private final static int MAGIC_FACTOR3 = 1566083941;
  private final static int MAGIC_MASK1  = 0x9d2c5680;
  private final static int MAGIC_MASK2  = 0xefc60000;
  private final static int MAGIC_SEED  = 19650218;
  private final static long DEFAULT_SEED = 5489L;

  // Internal state
  private transient int[] mt;
  private transient int mti;
  private transient boolean compat = false;

  // Temporary buffer used during setSeed(long)
  private transient int[] ibuf;

  /**
   * The default constructor for an instance of MTRandom. This invokes
   * the no-argument constructor for java.util.Random which will result
   * in the class being initialised with a seed value obtained by calling
   * System.currentTimeMillis().
   */
  public MTRandom() { }

  /**
   * This version of the constructor can be used to implement identical
   * behaviour to the original C code version of this algorithm including
   * exactly replicating the case where the seed value had not been set
   * prior to calling genrand_int32.
   * <p>
   * If the compatibility flag is set to true, then the algorithm will be
   * seeded with the same default value as was used in the original C
   * code. Furthermore the setSeed() method, which must take a 64 bit
   * long value, will be limited to using only the lower 32 bits of the
   * seed to facilitate seamless migration of existing C code into Java
   * where identical behaviour is required.
   * <p>
   * Whilst useful for ensuring backwards compatibility, it is advised
   * that this feature not be used unless specifically required, due to
   * the reduction in strength of the seed value.
   *
   * @param compatible Compatibility flag for replicating original
   * behaviour.
   */
  public MTRandom(boolean compatible) {
    super(0L);
    compat = compatible;
    setSeed(compat?DEFAULT_SEED:System.currentTimeMillis());
  }

  /**
   * This version of the constructor simply initialises the class with
   * the given 64 bit seed value. For a better random number sequence
   * this seed value should contain as much entropy as possible.
   *
   * @param seed The seed value with which to initialise this class.
   */
  public MTRandom(long seed) {
    super(seed);
  }

  /**
   * This version of the constructor initialises the class with the
   * given byte array. All the data will be used to initialise this
   * instance.
   *
   * @param buf The non-empty byte array of seed information.
   * @throws NullPointerException if the buffer is null.
   * @throws IllegalArgumentException if the buffer has zero length.
   */
  public MTRandom(byte[] buf) {
    super(0L);
    setSeed(buf);
  }

  /**
   * This version of the constructor initialises the class with the
   * given integer array. All the data will be used to initialise
   * this instance.
   *
   * @param buf The non-empty integer array of seed information.
   * @throws NullPointerException if the buffer is null.
   * @throws IllegalArgumentException if the buffer has zero length.
   */
  public MTRandom(int[] buf) {
    super(0L);
    setSeed(buf);
  }

  // Initializes mt[N] with a simple integer seed. This method is
  // required as part of the Mersenne Twister algorithm but need
  // not be made public.
  private final void setSeed(int seed) {

    // Annoying runtime check for initialisation of internal data
    // caused by java.util.Random invoking setSeed() during init.
    // This is unavoidable because no fields in our instance will
    // have been initialised at this point, not even if the code
    // were placed at the declaration of the member variable.
    if (mt == null) mt = new int[N];

    // ---- Begin Mersenne Twister Algorithm ----
    mt[0] = seed;
    for (mti = 1; mti < N; mti++) {
      mt[mti] = (MAGIC_FACTOR1 * (mt[mti-1] ^ (mt[mti-1] >>> 30)) + mti);
    }
    // ---- End Mersenne Twister Algorithm ----
  }

  /**
   * This method resets the state of this instance using the 64
   * bits of seed data provided. Note that if the same seed data
   * is passed to two different instances of MTRandom (both of
   * which share the same compatibility state) then the sequence
   * of numbers generated by both instances will be identical.
   * <p>
   * If this instance was initialised in 'compatibility' mode then
   * this method will only use the lower 32 bits of any seed value
   * passed in and will match the behaviour of the original C code
   * exactly with respect to state initialisation.
   *
   * @param seed The 64 bit value used to initialise the random
   * number generator state.
   */
  public final synchronized void setSeed(long seed) {
    if (compat) {
      setSeed((int)seed);
    } else {

      // Annoying runtime check for initialisation of internal data
      // caused by java.util.Random invoking setSeed() during init.
      // This is unavoidable because no fields in our instance will
      // have been initialised at this point, not even if the code
      // were placed at the declaration of the member variable.
      if (ibuf == null) ibuf = new int[2];

      ibuf[0] = (int)seed;
      ibuf[1] = (int)(seed >>> 32);
      setSeed(ibuf);
    }
  }

  /**
   * This method resets the state of this instance using the byte
   * array of seed data provided. Note that calling this method
   * is equivalent to calling "setSeed(pack(buf))" and in particular
   * will result in a new integer array being generated during the
   * call. If you wish to retain this seed data to allow the pseudo
   * random sequence to be restarted then it would be more efficient
   * to use the "pack()" method to convert it into an integer array
   * first and then use that to re-seed the instance. The behaviour
   * of the class will be the same in both cases but it will be more
   * efficient.
   *
   * @param buf The non-empty byte array of seed information.
   * @throws NullPointerException if the buffer is null.
   * @throws IllegalArgumentException if the buffer has zero length.
   */
  public final void setSeed(byte[] buf) {
    setSeed(pack(buf));
  }

  /**
   * This method resets the state of this instance using the integer
   * array of seed data provided. This is the canonical way of
   * resetting the pseudo random number sequence.
   *
   * @param buf The non-empty integer array of seed information.
   * @throws NullPointerException if the buffer is null.
   * @throws IllegalArgumentException if the buffer has zero length.
   */
  public final synchronized void setSeed(int[] buf) {
    int length = buf.length;
    if (length == 0) throw new IllegalArgumentException("Seed buffer may not be empty");
    // ---- Begin Mersenne Twister Algorithm ----
    int i = 1, j = 0, k = (N > length ? N : length);
    setSeed(MAGIC_SEED);
    for (; k > 0; k--) {
      mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >>> 30)) * MAGIC_FACTOR2)) + buf[j] + j;
      i++; j++;
      if (i >= N) { mt[0] = mt[N-1]; i = 1; }
      if (j >= length) j = 0;
    }
    for (k = N-1; k > 0; k--) {
      mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >>> 30)) * MAGIC_FACTOR3)) - i;
      i++;
      if (i >= N) { mt[0] = mt[N-1]; i = 1; }
    }
    mt[0] = UPPER_MASK; // MSB is 1; assuring non-zero initial array
    // ---- End Mersenne Twister Algorithm ----
  }

  /**
   * This method forms the basis for generating a pseudo random number
   * sequence from this class. If given a value of 32, this method
   * behaves identically to the genrand_int32 function in the original
   * C code and ensures that using the standard nextInt() function
   * (inherited from Random) we are able to replicate behaviour exactly.
   * <p>
   * Note that where the number of bits requested is not equal to 32
   * then bits will simply be masked out from the top of the returned
   * integer value. That is to say that:
   * <pre>
   * mt.setSeed(12345);
   * int foo = mt.nextInt(16) + (mt.nextInt(16) << 16);</pre>
   * will not give the same result as
   * <pre>
   * mt.setSeed(12345);
   * int foo = mt.nextInt(32);</pre>
   *
   * @param bits The number of significant bits desired in the output.
   * @return The next value in the pseudo random sequence with the
   * specified number of bits in the lower part of the integer.
   */
  protected final synchronized int next(int bits) {
    // ---- Begin Mersenne Twister Algorithm ----
    int y, kk;
    if (mti >= N) {       // generate N words at one time

      // In the original C implementation, mti is checked here
      // to determine if initialisation has occurred; if not
      // it initialises this instance with DEFAULT_SEED (5489).
      // This is no longer necessary as initialisation of the
      // Java instance must result in initialisation occurring
      // Use the constructor MTRandom(true) to enable backwards
      // compatible behaviour.

      for (kk = 0; kk < N-M; kk++) {
        y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
        mt[kk] = mt[kk+M] ^ (y >>> 1) ^ MAGIC[y & 0x1];
      }
      for (;kk < N-1; kk++) {
        y = (mt[kk] & UPPER_MASK) | (mt[kk+1] & LOWER_MASK);
        mt[kk] = mt[kk+(M-N)] ^ (y >>> 1) ^ MAGIC[y & 0x1];
      }
      y = (mt[N-1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
      mt[N-1] = mt[M-1] ^ (y >>> 1) ^ MAGIC[y & 0x1];

      mti = 0;
    }

    y = mt[mti++];

    // Tempering
    y ^= (y >>> 11);
    y ^= (y << 7) & MAGIC_MASK1;
    y ^= (y << 15) & MAGIC_MASK2;
    y ^= (y >>> 18);
    // ---- End Mersenne Twister Algorithm ----
    return (y >>> (32-bits));
  }

  // This is a fairly obscure little code section to pack a
  // byte[] into an int[] in little endian ordering.

  /**
   * This simply utility method can be used in cases where a byte
   * array of seed data is to be used to repeatedly re-seed the
   * random number sequence. By packing the byte array into an
   * integer array first, using this method, and then invoking
   * setSeed() with that; it removes the need to re-pack the byte
   * array each time setSeed() is called.
   * <p>
   * If the length of the byte array is not a multiple of 4 then
   * it is implicitly padded with zeros as necessary. For example:
   * <pre>  byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }</pre>
   * becomes
   * <pre>  int[] { 0x04030201, 0x00000605 }</pre>
   * <p>
   * Note that this method will not complain if the given byte array
   * is empty and will produce an empty integer array, but the
   * setSeed() method will throw an exception if the empty integer
   * array is passed to it.
   *
   * @param buf The non-null byte array to be packed.
   * @return A non-null integer array of the packed bytes.
   * @throws NullPointerException if the given byte array is null.
   */
  public static int[] pack(byte[] buf) {
    int k, blen = buf.length, ilen = ((buf.length+3) >>> 2);
    int[] ibuf = new int[ilen];
    for (int n = 0; n < ilen; n++) {
      int m = (n+1) << 2;
      if (m > blen) m = blen;
      for (k = buf[--m]&0xff; (m & 0x3) != 0; k = (k << 8) | buf[--m]&0xff);
      ibuf[n] = k;
    }
    return ibuf;
  }
}

测试

测试代码

    // MT19937的Java实现
    MTRandom mtRandom=new MTRandom();
    Map<Integer,Integer> map=new HashMap<>();
    //循环次数
    int times=1000000;
    long startTime=System.currentTimeMillis();
    for(int i=0;i<times;i++){
      //使用Map去重
      map.put(mtRandom.next(32),0);
    }
    //打印循环次数
    System.out.println("times:"+times);
    //打印Map的个数
    System.out.println("num:"+map.size());
    //打印非重复比率
    System.out.println("proportion:"+map.size()/(double)times);
    //花费的时间(单位为毫秒)
    System.out.println("time:"+(System.currentTimeMillis()-startTime));

测试结果

times:1000000
num:999886
proportion:0.999886
time:374

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java随机数算法原理与实现方法实例详解

    本文实例讲述了Java随机数算法.分享给大家供大家参考,具体如下: 软件实现的算法都是伪随机算法,随机种子一般是系统时间 在数论中,线性同余方程是最基本的同余方程,"线性"表示方程的未知数次数是一次,即形如: ax≡b (mod n)的方程.此方程有解当且仅当 b 能够被 a 与 n 的最大公约数整除(记作 gcd(a,n) | b).这时,如果 x0 是方程的一个解,那么所有的解可以表示为: {x0+kn/d|(k∈z)} 其中 d 是a 与 n 的最大公约数.在模 n 的完全剩余系

  • 史上最全的java随机数生成算法分享

    复制代码 代码如下: String password = RandomUtil.generateString(10); 源码如下: 复制代码 代码如下: package com.javaniu.core.util;import java.util.Random;public class RandomUtil { public static final String ALLCHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRS

  • java生成抽样随机数的多种算法

    本章先讲解Java随机数的几种产生方式,然后通过示例对其进行演示. 概述: 这里你是不是会说,生成随机数有什么难的?不就是直接使用Java封装好了的random就行了么?当然对于一般情况下是OK的,而且本文要说明的这些算法也是基于这个random库函数的. 本文主要是针对抽样这一行为进行的,而抽样本身有一个隐含的规则就是不要有重复数据.好了,有了这些说明.你可以先尝试着用一些自己的想法来实现不重复地生成随机数. 算法尝试: 一些好的算法出现,往往伴随着一些不那么好的算法.但是对于效果不太好的算法

  • java随机数生产算法实例

    java提供了Math.random()函数,返回一个double类型的随机数,也有util包里的Random类,可以生成double,int,float,long,bytes等随机数. 但有些业务需求,往往需要对这些方法做一下封装.比如用固定因子生成32位的3DES算法key值. 下面提供一些封装的方法: package test; import java.util.Random; public class RandomUtil { public static final String ALL

  • Java实现高效随机数算法的示例代码

    前言 事情起源于一位网友分享了一个有趣的面试题: 生成由六位数字组成的ID,要求随机数字,不排重,不可自增,且数字不重复.ID总数为几十万. 初次解答 我一开始想到的办法是 生成一个足够大的ID池(其实就是需要多少就生成多少) 对ID池中的数字进行随机排序 依次消费ID池中的数字 可惜这个方法十分浪费空间,且性能很差. 初遇梅森旋转算法 后面咨询了网友后得知了一个高效的随机数算法:梅森旋转(Mersenne Twister/MT).通过搜索资料得知: 梅森旋转算法(Mersenne twiste

  • Java实现基本排序算法的示例代码

    目录 1. 概述 2. 插入排序 2.1 直接插入排序 2.2 希尔排序(缩小增量排序) 3. 选择排序 3.1 直接选择排序 3.2 堆排序 4. 交换排序 4.1 冒泡排序 4.2 快速排序 5. 归并排序 6. 计数排序(非比较类型的排序) 7. 排序算法总结 1. 概述 排序概念:就是将一串记录按照其中某个或某些关键字的大小,递增或递减的排列起来的操作. 稳定性:通俗的将就是数据元素不发生有间隔的交换,例如: 内部排序:数据元素全部放在内存中的排序 外部排序:数据元素太多不能一次加载到内

  • Java实现拓扑排序算法的示例代码

    目录 拓扑排序原理 1.点睛 2.拓扑排序 3.算法步骤 4.图解 拓扑排序算法实现 1.拓扑图 2.实现代码 3.测试 拓扑排序原理 1.点睛 一个无环的有向图被称为有向无环图.有向无环图是描述一个工程.计划.生产.系统等流程的有效工具.一个大工程可分为若干子工程(活动),活动之间通常有一定的约束,例如先做什么活动,有什么活动完成后才可以开始下一个活动. 用节点表示活动,用弧表示活动之间的优先关系的有向图,被称为 AOV 网. 在 AOV 网中,若从节点 i 到节点 j 存在一条有向路径,则称

  • Java实现折半插入排序算法的示例代码

    目录 排序算法介绍 折半插入排序 原理 代码实现 复杂度分析 算法实践 排序算法介绍 排序算法是通过特定的算法因式将一组或多组数据按照既定模式进行重新排序.最终序列按照一定的规律进行呈现. 在排序算法中,稳定性和效率是我们经常要考虑的问题. 稳定性:稳定是指当两个相同的元素同时出现于某个序列之中,则经过一定的排序算法之后,两者在排序前后的相对位置不发生变化. 复杂度分析: (1)时间复杂度:即从序列的初始状态到经过排序算法的变换移位等操作变到最终排序好的结果状态的过程所花费的时间度量. (2)空

  • Java实现查找算法的示例代码(二分查找、插值查找、斐波那契查找)

    目录 1.查找概述 2.顺序查找 3.二分查找 3.1 二分查找概述 3.2 二分查找实现 4.插值查找 4.1 插值查找概述 4.2 插值查找实现 5.斐波那契查找 5.1 斐波那契查找概述 5.2 斐波那契查找实现 5.3 总结 1.查找概述 查找表: 所有需要被查的数据所在的集合,我们给它一个统称叫查找表.查找表(Search Table)是由同一类型的数据元素(或记录)构成的集合. 查找(Searching): 根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录).

  • Java实现抽奖算法的示例代码

    目录 一.题目描述 二.解题思路 三.代码详解 四.优化抽奖算法 解题思路 代码详解 一.题目描述 题目: 小虚竹为了给粉丝送福利,决定在参与学习打卡活动的粉丝中抽一位幸运粉丝,送份小礼物.为了公平,要保证抽奖过程是随机的. 二.解题思路 1.把参与的人员加到集合中 2.使用Random对象获取随机数 3.把随机数当下标,获取集合中的幸运用户 三.代码详解 public class Basics28 { public static void main(String[] args) { List<

  • Java语言字典序排序算法解析及代码示例

    字典序法就是按照字典排序的思想逐一产生所有排列. 在数学中,字典或词典顺序(也称为词汇顺序,字典顺序,字母顺序或词典顺序)是基于字母顺序排列的单词按字母顺序排列的方法. 这种泛化主要在于定义有序完全有序集合(通常称为字母表)的元素的序列(通常称为计算机科学中的单词)的总顺序. 对于数字1.2.3......n的排列,不同排列的先后关系是从左到右逐个比较对应的数字的先后来决定的.例如对于5个数字的排列 12354和12345,排列12345在前,排列12354在后.按照这样的规定,5个数字的所有的

  • Java实现8种排序算法的示例代码

    冒泡排序 O(n2) 两个数比较大小,较大的数下沉,较小的数冒起来. public static void bubbleSort(int[] a) { //临时变量 int temp; //i是循环次数,也是冒泡的结果位置下标,5个数组循环5次 for (int i = 0; i < a.length; i++) { //从最后向前面两两对比,j是比较中下标大的值 for (int j = a.length - 1; j > i; j--) { //让小的数字排在前面 if (a[j] <

  • JAVA用递归实现全排列算法的示例代码

    求一个n阶行列式,一个比较简单的方法就是使用全排列的方法,那么简述以下全排列算法的递归实现. 首先举一个简单的例子说明算法的原理,既然是递归,首先说明一下出口条件.以[1, 2]为例 首先展示一下主要代码(完整代码在后面),然后简述 //对数组array从索引为start到最后的元素进行全排列 public void perm(int[]array,int start) { if(start==array.length) { //出口条件 for(int i=0;i<array.length;i

  • Java实现雪花算法的示例代码

    一.介绍 SnowFlow算法是Twitter推出的分布式id生成算法,主要核心思想就是利用64bit的long类型的数字作为全局的id.在分布式系统中经常应用到,并且,在id中加入了时间戳的概念,基本上保持不重复,并且持续一种向上增加的方式. 在这64bit中,其中``第一个bit是不用的,然后用其中的41个bit作为毫秒数,用10bit作为工作机器id,12bit`作为序列号.具体如下图所示: 第一个部分:0,这个是个符号位,因为在二进制中第一个bit如果是1的话,那么都是负数,但是我们生成

随机推荐