700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 一种简单的生成伪随机数的方法

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

时间:2022-08-10 12:14:45

相关推荐

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

论文原文:/p-5405273318696.html

我对原文的翻译:/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)@SpringBootTestpublic class FactoryApplicationTests {@AutowiredRegistCenterDao registCenterDao;@AutowiredRulesDao rulesDao;@AutowiredSourceNumTabDao sourceNumTabDao;@Testpublic 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,一样保证是唯一的。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。