一种简单的生成伪随机数的方法

论文原文:https://www.doc88.com/p-5405273318696.html

我对原文的翻译:https://blog.csdn.net/star1210644725/article/details/100169402

这个算法要解决的问题是:生成一个随机的订单号,又要保证绝对唯一

我的思路主要是根据论文原文来的!大家觉得有问题,完全可以自己看论文,研究论文。

 

 # # 什么叫伪随机?

  这个算法目的是要生成一个唯一的订单号,比方说是八位数字 00000001   10000000,要保证绝对唯一,下边根据规则再讲什么是伪随机。

  •   唯一性

  就拿中铁,12306的订单号来说,必须确保的是订单号是唯一的,因为我们在取票的时候就是根据唯一的订单号去保证拿到唯一的一张票,如果两张订单出现单号唯一的话就会出现这样的问题,比方说 0000001,假如这个订单号出现两次,还是同一天,一个人买了从北京去上海的票,一个人买了从北京去青岛的票,如果订单号重复了,那么,出票的时候就要出问题了。去是上海的给了去青岛的票,这是不行的。

  •   随机性

  比方说用户拿到票,看到了订单号是 00000001,如果这个单号知识单纯的递增,那么下一次就是 00000002,这是完全可以猜到 ,我自己取了票,然后猜下别人的订单号,拿这个订单号去猜别人的订单号,然后取票。 这就是有问题的。所以要保证是随机的。

 

  # # 伪随机

  唯一性,随机性,是两条必须要满足的规则。也是要求。

  伪随机是实现的手段,具体是如何实现的,接着看规则,这是我根据论文来理解的。

 

  # # 实现伪随机

  解决思路是这样的:比方说八位数的订单号,从 00000001 ~ 99999999  这就有一亿个订单号,这在一个月之内是够用的。不能直接拿着 00000001 直接使用,要想实现随机,解决方案就是 交换数字的位置, 比方说 个位换到十位,十位换到百位,以此类推,根据排列组合,可以有 8*7*6*5*4*3*2*1 = 40320个交换方案,去交换数字。那么 00000001 ,这个数使用一个规则去交换,下次用00000002 这个数去交换,下次用 00000003 去交换,这就叫伪随机,我们始终是根据一个有序的数字去做交换的,交换出来的看起来似乎是 没有规则的,这就是伪随机。

  问题来了,那就是交换规则的选择,比方说 00000011 和 00000101,这两个去交换位置的时候就有点要求了,比方说第一个数字十位换百位,第二个数字用到了千位换万位,最后两个最终得到的都是 00000101 ,那么唯一性就丢失的,这是不符合我们的要求的。

  根据论文里边的思想,使用一个规则就可以巧妙的解决掉这个问题,那就是 数字串的各个位置拆分开来,然后求和,和相等的串必须用相同的交换规则,就可以将上边提到的问题给解决的,比如,00000011 和 00000101  拆位求和等于 2 ,都等于2 就要用相同的交换规则去换位。 比方说第一个串用的 十位换百位的交换规则,第二个就一定要使用 十位换百位的规则,最终得到是是00000101 和00000011 按照这个规则去换,就一定不会出问题,不会出现上边的交换以后出现重复的可能。

  交换方案的个数,就是排列组合,八位就是 8*7*6*5*4*3*2*1 = 40320,也就是有40320 这么多方案去选择。就算有人要恶意的去猜测,那就需要他在 这么多规则里边去一个一个破解了。用户看到的是母数交换之后得到的,也不能从订单中获得母数的信息。这是没法去猜的,不知道规则,不知道母数,这就可以了。这样随机出来的数,是可以用来做订单号的。

  有这么多交换方案,我们能用到也只有一部分,举个例子, 八位能用的交换方案个数是 9+9+9+9+9+9+9+9+9 = 72,

这个计算规则这这么来的,以为 拆位和 相等的就必须用同一个规则,00000011 和11000000 用同一个规则, 各个位加起来的和只用 72 种,那就去这 四万多个交换规则中去选取 72 个就可以了。再举个例子,三位数 001 和 100 ,根据排列组合 3*2*1 =6,而 9+9+9=27  ,但是交换规则只有6种,这并不影响,因为27 个可以去重复使用 6种规则中的一种。这样做是完全可以的。甚至说。我们都只用一个交换规则都是没有问题的。为了避免别人故意去猜测这个规则,我们用 八位数的,有四万多个规则,别人去猜的话,就要去这四万多个规则中去猜。这就是意义所在。

 

  # # 规则讲完了,再说 代码思路。

  我把 有规则的从 00000001 开始到 99999999 结束的叫做 母数。需要有一个寄存器去记录到这个数到哪里了,然后用过以后,母数要递增 1 的。

  将母数拆分开,求和。求和以后要在注册中心选用一个规则,比方说 00000002 第一次去注册中心,因为拆位得到的是 2 ,这是第一次进来,那么就可以去选用一条交换规则。如果00000011 再次进来,拆位以后求和得到 是 2,2 在注册中心已经注册过了,那么就需要选用已经有的规则去使用。

  制定交换的规则

  做一个注册中心,比方说 母数拆分完得到的一个和就放在注册中心,然后与之对应的是一个规则。

  然后按照一定的规则去交换母数。

  最后返回母数交换后的一个串。

 

 看具体的代码(简单版思路),因为只是一个方法,不能保留现场,注册中心无法存储,再次进来之后,规则都是新的。所以是有问题的。 大家可以先看思路。

import java.util.HashMap;

public class DDMForId {
    public static void main(String[] args) {
        System.out.println(getUniquenessId());
    }
    public static StringBuilder getUniquenessId(){
        /**
         * 不出现重复,均匀分布,没有周期规律
         */

        //母数(母数位数确定下来,集合规则的个数就能确定下来)
        long currentTime=System.currentTimeMillis();
        String min = "12345";
        //结果要返回的 id串儿
        StringBuilder resultId= new StringBuilder("");
        //初始化注册中心,初始化为最少72,最好为2的次方
        HashMap registCenter = new HashMap(72);
        // 拆分母数,求和(和作为标记),在注册中心作为主键,存在就代表已经有了规则。
        char[] temp = min.toCharArray();
        int registKey = 0;
        int[] tempresult = new int[8] ;
        for(int i=0; i< temp.length;i++){
            //标识求和
            registKey += (temp[i]-48);
            //拆分以后直接放在临时数组中,用来交换。
            tempresult[i] = (temp[i]-48);
            System.out.println( tempresult[i]);
        }
        //规则集合 (集合规则和母数的位数有关系,需要用一个合适的数据结构存储)
        int[] rules = {12,13,14,24,42};
        //选择规则集合(选择集合之前要去判断规则是否已注册,判断的标准就是标记位是否已经被注册,如果被注册就应该去找到已经使用的规则)
        //某个Key第一次到里边注册中心来
        int tempNum = 0;
        if(registCenter.get(registKey) == null){
            int random = (int)(Math.random()*5);
            //交换原来的母数
            System.out.println(random+"**************************");
            tempNum = tempresult[rules[random]%10];
            tempresult[rules[random]%10] =  tempresult[rules[random]/10];
            tempresult[rules[random]/10] = tempNum;

            for(int i=0; i< temp.length;i++){
                //需要优化
                resultId.append(tempresult[i])  ;
                System.out.println(resultId);
            }
            registCenter.put(registKey,rules[random]);
            //每次执行完将母数加一
            min = (Integer.parseInt(min))+1+"";
            System.out.println(min);
        }else{
            int num = (int)(registCenter.get(registKey));
            tempNum = tempresult[num%10];
            tempresult[num%10] =  tempresult[num/10];
            tempresult[num/10] = tempNum;

            for(int i=0; i< temp.length;i++){
                //需要优化,用Stringbuffer更好一点,节省内存。
                resultId.append(tempresult[i]);
                System.out.println(resultId);
            }
            min = (Integer.parseInt(min))+1+"";
        }
        System.out.println(" 总执行时间//");
        System.out.println(System.currentTimeMillis()-currentTime);
        return resultId;
    }
}

 

 

    上边说到了,现场无法保留,我用借用数据库来操作。接着看代码。

    我先说一下,这段代码是实现思路,不能直接拿来跑。需要连接数据库,等等。主要看思路把!

 

package com.angus.gzky.factory;

import com.angus.gzky.factory.entity.RegistCenter;
import com.angus.gzky.factory.entity.SourceNumTab;
import com.angus.gzky.factory.repostory.RegistCenterDao;
import com.angus.gzky.factory.repostory.RulesDao;
import com.angus.gzky.factory.repostory.SourceNumTabDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.management.relation.Role;

@RunWith(SpringRunner.class)
@SpringBootTest
public class FactoryApplicationTests {

    @Autowired
     RegistCenterDao registCenterDao;
    @Autowired
     RulesDao rulesDao;
    @Autowired
     SourceNumTabDao sourceNumTabDao;
    @Test
    public void contextLoads() {
    }
    @Test
    /**
     * 订单号不可出现重复,均匀分布,没有周期规律
     */
    public void getUniquenessId(){
        long currentTime=System.currentTimeMillis();
        //从寄存器中获取母数
        String sourceNum = sourceNumTabDao.getOne(1).getSourceNum();
        //定义结果要返回的 id串儿
        StringBuilder resultId= new StringBuilder();
        // 拆分母数,求和(和作为标记),在注册中心作为主键,存在就代表已经有了规则。

        char[] temp = sourceNum.toCharArray();
        int registKey = 0;
        int[] tempresult = new int[8];
        for(int i=0; i< temp.length;i++){
            //标识求和
            registKey += (temp[i]-48);
            //拆分以后直接放在临时数组中,用来交换。
            tempresult[i] = (temp[i]-48);
            System.out.println( tempresult[i]);
        }

        //如果拆位得到的标识在注册中心不存在
        long currentTime2=System.currentTimeMillis();
        if(registCenterDao.findByRegistKey(registKey) == null){
            System.out.println((System.currentTimeMillis()-currentTime2)+"-----------------------------");
            int random = (((int)(Math.random()*5))+1);

            int rule = rulesDao.getOne(random).getRule();
            //交换原来的母数,并获得最终的id,最后将这个返回就可以了*********
            resultId = changeSourceNumByRule( rule, tempresult);
            //第一次进来注册中心没有,就在注册中心注册一下
            updateRegistCenter(registKey,rule);
            //更新一下寄存器
            updateSourceNum(sourceNum);

            System.out.println(" 总执行时间*******************");
            System.out.println(System.currentTimeMillis()-currentTime);
        }else{
            System.out.println((System.currentTimeMillis()-currentTime2)+"-----------------------------");
            registCenterDao.findByRegistKey(registKey).getRule();
            int rule = registCenterDao.findByRegistKey(registKey).getRule();
            //返回最终的id***********************
            resultId = changeSourceNumByRule(rule, tempresult);
            //更新一下寄存器
            updateSourceNum(sourceNum);
            System.out.println(" 总执行时间//");
            System.out.println(System.currentTimeMillis()-currentTime);
        }
    }
    /**
     * 传入当前的 母数,然后更新寄存器。
     * @param sourceNum
     */
    public void updateSourceNum(String sourceNum){
       // sourceNum = (Integer.parseInt(sourceNum))+1+"";
        //少于八位数需要补位
        int sourceNumLength = ((Integer.parseInt(sourceNum))+1+"").length();
        String temp = (Integer.parseInt(sourceNum)+1+"");
        while((8-sourceNumLength)!=0){
            temp = "0"+temp;
            sourceNumLength = sourceNumLength+1;
        }
        System.out.println(temp);
        SourceNumTab sourceNumTab = new SourceNumTab();
        sourceNumTab.setId(1);
        sourceNumTab.setSourceNum(temp);
        sourceNumTabDao.save(sourceNumTab);
    }

    /**
     * 根据规则交换母数
     * @param rule
     * @param tempresult
     * @return
     */
    public StringBuilder changeSourceNumByRule(int rule,int[] tempresult){
        StringBuilder resultId= new StringBuilder("");
        //由规则分割的数组
        System.out.println("rule*****"+rule);
        int temp= rule;
        int[] helptemp = new int[8];
        for(int i=helptemp.length-1; i>=0; i-- ){
            helptemp[i] = temp%10;
            temp =temp/10;
        }
        int[] finaResult = new int[8];
        for (int i=0; i<finaResult.length; i++){
            finaResult[i] = tempresult[helptemp[i]-1];
        }
        //最终完成交换的数组

        for(int i=0; i< finaResult.length;i++){
            resultId.append(finaResult[i]);
        }
        System.out.println("resultId********:"+ resultId);
        return resultId;
    }

    /**
     * 在注册中心注册规则
     * @param registKey
     * @param rule
     */
    public void updateRegistCenter(int registKey, int rule){
        RegistCenter registCenter = new RegistCenter();
        registCenter.setRegistKey(registKey);
        registCenter.setRule(rule);
        registCenterDao.save(registCenter);
    }
   
    
}

  可以看到我打了时间戳,我这是用来计算执行时间的。

 

  另外这个算法执行的时间太长了,需要多次数据库操作,需要花费时间太长。还可以继续优化,减少数据库的操作。换用redis基于内存的数据库,效果会好很多。

  最后如何应对高并发,应对分布式。也是需要考虑的。 我的思路是,如果单台服务器就考虑使用redis了,完全使用这个数据库去操作。需要解决持久化的问题。 分布式的话,可以选择每台机器都用自己的母数,都从00000001开始,我们可以选择在前边添加 字母来代表服务器节点。A00000001 ,B00000001 ,这就代表两台服务器去生成 订单号了。他们之间是没有关联的,你生成你的,我生成我的。加了标识 A 或 B,一样保证是唯一的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值