无序唯一ID生成算法

最近遇到一个需要生成无序唯一ID的问题,虽然只是个小问题,也花了一些时间思考,因此记录一下。以下介绍几种方法。

一、直接生成随机数

1. GUID

这个方法应该最容易想到,GUID就是一个利用随机算法生成的128bit的随机数,并不保证前后生成的两个数字不相同,但由于长度足够长,所以生成的两个数字重复的概率非常小,可以认为是不会重复的。
然而这么长的数字并不适合用作会议号,所以该方案不合适。

2.随机数+查重

这个方法也很容易想到:
1 生成随机数
2 查询数据库,生成的随机数是否已被使用
2-1 如果已被使用,则回到步骤1,重新生成
2-2 如果未被使用,则使用该数字,并记录到数据库

这种方法缺点是需要查重,如果到后期,已经使用的数字较多,容易出现重复的话,可能会出现生成一个数字需要多次查询数据库的情况,影响效率。
如果会使用的号码个数不多,这种方法是可以考虑的。

3.号码池

1 在数据库中建一个号码池表,保存一定数量的有序的号码,例如10010000~10020000
2 需要获取一个号码时,生成一个随机数(小于号码池当前号码个数)作为序号,取出号码池中当前排序为该序号的号码,作为结果
3 已使用的号码删除

该方法相对于查重的方式,优点是不需要重复获取,可以保证查一次数据库就获取到号码。
但是由于不可能保存太多的号码到号码池,所以随机的范围比较小,且会占用一定的数据库空间。

二、有序ID为基础进行映射

无序唯一ID,有两个要求:无序,唯一。唯一通过有序(递增)很容易实现,因此以有序ID为基础,再实现一个唯一映射的算法,即可达到唯一+无序的效果。

1. 查表

预先生成一个随机表,表中的数字不重复。以有序ID为序号,查询随机表得到随机数。
数字范围较大时,随机表的大小也会比较大,占用资源,因此需要做一些拆分。

以10进制为例,例如使用如下一张表,上面一行是序号(输入),下面一行是取值(输出)。

序号0123456789
取值9785634120

我们以有序ID为基础,因此有一个有序ID,例如12345678,我们将每一位作为输入在上表中进行查询转换,得到结果:

原值12345678
转换78563412

转换结果是:7856412,看起来就比较随机了。

我们可以再做一些改进,例如:
1.每一位用不同的表。
2.修改进制,也就是表的大小,例如100进制,就是每次用两位10进制进行查询。

但有一个问题无法解决:如果两个有序ID只有1位不同,生成的结果也只有1位不同(当然,如果是100进制那就是两位不同),容易看出规律,不算特别随机。有点加密的味道了。。

2.凯撒(Caesar)加密即改进

既然已经有了加密的味道,就找了一些加密算法,如果加密算法有1.原文与密文等长 2.可解密 这两个特点,就可以作为我们的转换算法了。
caesar加密是一个比较简单的加密算法,也可以用在这里,但是随机性不强,也容易看出特点。

caesar加密:
其实也就是查表,只是这个表比较特殊:“明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文”。
假设固定数目为2,那在10进制中就是:1变3,2变4...

我们可以对它做一点改进:
假设原文是8为数字,写成数组:a[1~8],固定的便宜是2,转换结果是数字b[1~8],那么原算法就是b[n] = (a[n]+2) mod 10
我们可以再对每一位加一个额为的偏移k[1~8],每一位随机生成,即b[n] = (a[n]+k[n]+2) mod 10,这样结果会更随机一点,但其实还是与查表没什么差别。

我们再做一点改进:
计算a[1]~a[8]的和,再mod 10得到一个值,记为s,使b[n] = (a[n] + k[n] + s + 2) mod 10。
首先得说一下这个结果在一些情况下是不可解密的,因此实际是一次失败的尝试。。但是其思想可以作为参考:每一位数字都会影响其他位,所以即是只有一位不同,原文与结果的差距也是很大的。

再说一下在什么情况下可以解密,也即可以作为唯一映射使用。
我们先看一个失败的例子:只考虑a[n]和s,即b[n]=(a[n]+s) mod 10,我们对两个数111和666进行转换。
111:s=3,所以转换为444
666:s=(3*6)mod10=8,所以转换为444
两个数字的转换结果是一样的,显然不是唯一映射。

两个数字a1[1-8]和a2[1-8]的转换结果一样,有一个必要非充分条件:

a1[1]-a2[1]=a1[2]-a2[2]=a1[3]-a2[3]=...,

即每一位的差值都是一样的,否则转换出来的b显然是不相同的。
那么我们可以设a2[n]=(a1[n]+k) mod 10,则s2=(a1[n]+k+a2[n]+k+...) mod 10 = (s1 + k*n) mod 10
如果转换结果相等,有:

b1[n]=b2[n]
即 (a1[n]+s1) mod 10 = (a2[n]+s2) mod 10
即 (a1[n]+s1) mod 10 = (a1[n]+k+s1+k*n) mod 10
即 (a1[n]+s1) mod 10 = (a1[n]+s1+k*(n+1)) mod 10

两边要相等,必然有k*(n+1) mod 10 = 0,也即只要该式不成立,这种转换就是唯一映射了。
k是1-9之间的任意一个数,则n+1与10不能有公约数,即不能是2或者5的倍数。也即在n+1=3,7,9等情况下,该转换是可行的。

3. AES加密

我们再看一个更高级的AES加密算法。
简单介绍一下AES加密算法的加密步骤:

for (i = 1; i < 加密轮次; i++) {
  字节代换;
  行位移;
  列混合;
  轮密钥加;
}

字节代换:AES处理时,小单位是1个byte,大单位是16个byte。字节代换是对一个byte的操作,即通过查一个预设表,将每个byte进行转换。
行位移:这是针对16个byte为单位的操作,将16个byte写成4*4的矩阵,矩阵的每一行分别向左位移0、1、2、3
列混合:这也是针对16个byte为单位的操作,还是写成4*4的矩阵,并与另一个固定的矩阵相乘,得到新的结果。关键在于使用了伽罗华域GF(2^8),加减乘除都是在有限域上进行的。
轮密钥加:与16byte的密钥进行异或。每一轮的密钥都不一样,根据上一轮的密钥与特定算法生成

具体的可以参考:
https://blog.csdn.net/zxh2075/article/details/80630296

AES加密对位数有要求,得是128位的二进制数,限制比较大。当然,如果使用2*2,3*3的矩阵,位数是不一样的,但是限制还是比较大。
实际上只是产生随机ID的话,使用列混合就足够了,基本上已经够随机了。

4.列混合改编

我们将输入值写成p进制数,p取2、3、5、7等质数。
设每一位分别为:a[1] a[2] ... a[n]
转换方法:


矩阵K是预设的矩阵,也即:

 

b[1] = k[1][1]*a[1]+k[1][2]*a[2]+...+k[1][n]*a[n]
b[2] = k[2][1]*a[1]+k[2][2]*a[2]+...+k[2][n]*a[n]
...
b[n] = k[n][1]*a[1]+k[n][2]*a[2]+...+k[n][n]*a[n]

用矩阵表示就是K*A=B,要解密就是K1*B=A,K1是K的逆矩阵。
关键还是在于要使用伽罗华域(有限域),这样才能实现结果在0~p-1之间的加减乘除运算,并满足运算规律。伽罗华域GF(p)要求p必须是素数,所以我们的进制也必须使用素数,则实际的可用范围就并非是0-1000000这种比较整的范围了,这是其缺点。

以我们的项目举个例子:生成900 000 000~999 999 999之间的随机数,可以认为就是生成000 000 000 ~ 99 999 999之间的随机数,再加上900 000 000。
我们取进制p为463,取3位数字,463*463*463=99252847,即无法产生99252847及以上的数字,影响不大。
矩阵随意取一个可逆的:


计算得到逆矩阵(逆矩阵计算描述起来比较复杂,这里就不写了,和普通矩阵类似,就是出现0~462之外的数时,就对463取模;另外除法转换成乘上乘法逆元,参考伽罗华域的相关资料):


验证一下是否逆矩阵:


对463取模得到E矩阵,确实是逆矩阵。

 

再试试转几个数字:
输入12345678=246+273*463+57*463^2,所以463进制下的三位分别是:246,273,57
用预设的矩阵乘上这3位:


验证一下逆转换:


逆转换成功,再把得到的结果转为10进制:63+266*463+431*463^2=92516260,和12345678差别很大了

 

再试试差1的12345679作为输入:
12345679=247+273*463+57*463^2


转换成10进制:64+317*463+39*463^2=8507226

 

比较两个数转换的结果:

转换前1234567812345679
转换后925162608507226

相近的两个数的转换结果相差也是很大的,效果不错。也可以参考AES,再搭配查表、密钥加、多轮次等做法,使得结果更随机,不过必要性也不大了,毕竟不是加密,只是生成一个比较随机的数。

这种方法的缺点就是进制收到质数的限制,因此与实际的10进制取值范围有一些差距。在会议号的生成中这个缺点也还可以接受,取一个合适的质数,差距也不会很大。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值