我的webapplication在数据库中有一个表,其中id
列对于每一行始终是唯一的。除此之外,我想要另一个名为code
的列,它将有一个6位数的唯一字母数字代码,数字0-9和字母A-Z。字母和数字可以在代码中重复。即FFQ77J
。我知道这个6位字母数字代码的唯一性随着时间的推移而减少,因为添加了更多行但是现在我对此感到满意。
要求(更新) - 代码应至少为6长 - 每个代码应为字母数字
所以我想生成这个字母数字代码。
问题
这样做的好方法是什么?
对于这一代,我将使用我在this answer
中看到的类似内容char[] symbols = new char[36];
char[] buf;
for (int idx = 0; idx < 10; ++idx)
symbols[idx] = (char) ('0' + idx);
for (int idx = 10; idx < 36; ++idx)
symbols[idx] = (char) ('A' + idx - 10);
public String nextString()
{
for (int idx = 0; idx < buf.length; ++idx)
buf[idx] = symbols[random.nextInt(symbols.length)];
return new String(buf);
}
答案 0 :(得分:3)
由于要求不的短代码是可猜测的,因此您不希望将其与您的uniqueID行ID绑定。否则,这意味着除了unique之外,你的rowID必须是随机的。从计数器0开始,递增,当代码为:000001,000002,000003等时,非常明显。
对于您的短代码,生成一个随机的32位int,省略符号并转换为base36。拨打您的数据库,以确保它可用。
您尚未明确提出可扩展性,但我认为了解设计的局限性非常重要。
在2 ^ 31个可能的6个char base36值中,您将有~65k行的碰撞(参见Birthday Paradox questions)
根据您的评论修改您的代码:
public String nextString()
{
return Integer.toString(random.nextInt(),36);
}
答案 1 :(得分:2)
我会这样做:
String s = Integer.toString(i, 36).toUpperCase();
选择base-36将使用字符0-9a-z作为数字。要获取使用大写字母的字符串(根据您的问题),您需要将结果折叠为大写。
如果您为自己的ID使用自动增量列,请将下一个值设置为至少60,466,176
,当呈现为基数为36时100000
- 始终为您提供一个6位数字。
答案 2 :(得分:2)
我会以0表示空表并执行
SELECT MAX(ID) FROM table
找到目前为止最大的id。将其存储在AtmoicInteger中并使用toString
进行转换AtomicInteger counter = new AtomicInteger(maxSoFar);
String nextId = Integer.toString(counter.incrementAndGet(), 36);
或用于填充。 36 ^^ 6 = 2176782336L
String nextId = Long.toString(2176782336L + counter.incrementAndGet(), 36).substring(1);
这将为您提供独特性,无需担心重复。 (它也不是随机的)
答案 3 :(得分:1)
要在小范围内创建随机但唯一的值 ,这里有一些我所知道的想法:
创建一个新的随机值并尝试插入。
让数据库约束捕获违规。此列也应该被编入索引。可能需要多次尝试DML,直到找到唯一ID。如上所述,这会随着时间的推移而导致更多的冲突(参见birthday problem)。
提前创建“免费ID”表,并在使用时将ID标记为正在使用(或从“免费ID”表中删除)。这类似于#1,但在工作完成后会发生变化。
这允许在另一个时间(可能是在cron作业期间)完成查找“免费ID”的工作,这样在插入过程中不会发生约束违规,使插入本身在整个使用过程中保持“相同的速度”所述域名。确保使用交易。
创建一个1-to-1/injective“混音器”功能,使输出“显示为随机”。重点是此功能必须是1比1才能避免重复。
此输出数将是“基数36编码”(这也是单射的);但只要输入(例如,自动增量PK)是唯一的,它就会保证唯一。这可能不如其他方法随机,但仍应创建一个漂亮的非线性输出。
可以相当简单地在8位查找表周围创建自定义内射函数 - 一次只处理一个字节并适当地改变映射。 我真的很喜欢这个想法,但它仍然可以带来一些可预测的输出
要查找空闲ID,上面的方法#1和#2可以使用“使用IN探测”来最小化所使用的SQL语句的数量。也就是说,生成随机值的束并使用IN
查询它们(记住您的数据库喜欢的IN大小),然后查看哪些值是免费的(因为没有结果) )。
要创建不占用这么小空间的唯一ID,GUID甚至散列(例如SHA1)可能会有用。但是,这些仅保证唯一性,因为它们具有126/160位空间,因此碰撞的可能性(对于不同的输入/时间空间)目前被认为是不可能的。
我其实非常喜欢使用内射函数的想法。请记住它是不良好的“随机”输出,请考虑这个伪代码:
byte_map = [0..255]
map[0] = shuffle(byte_map, seed[0])
..
map[n] = shuffle(byte_map, seed[1])
output[0] = map[0][input[0]]
..
output[n] = map[n][input[n]]
output_str = base36_encode(output[0] .. output[n])
虽然设置非常简单,但像0x200012和0x200054这样的数字仍将共享公共输出 - 例如0x1942fe和0x1942a9 - 虽然由于后来应用base-36编码,线路会有所改变。这可能会进一步改善,使其“看起来更随机”。
答案 4 :(得分:0)
简单地说,您可以使用Integer.toString(int i, int radix)
。由于您有36个基数(26个字母+10个数字),因此您将基数设置为36并将i
设置为整数。例如,要使用16501
,请执行:
String identifier=Integer.toString(16501, 36);
您可以使用.toUpperCase()
现在回答您的其他问题,是的,您应首先查询数据库以确保它不存在。如果依赖于数据库,它可能需要同步,或者可能不是因为它将使用自己的锁定系统。无论如何,您需要告诉我们哪个数据库。
关于是否有内置问题,我们也需要知道数据库类型。
答案 5 :(得分:0)
为了有效使用,请尝试在应用程序的HashSet<String>
中缓存生成的代码:
HashSet<String> codes = new HashSet<String>();
这样,您不必每次都进行数据库调用,以检查生成的代码是否唯一。您所要做的就是:
codes.contains(newCode);
而且,是的,您应该同步更新缓存的方法
public synchronize String getCode ()
{
String newCode = "";
do {
newCode = nextString();
}
while(codes.contains(newCode));
codes.put(newCode);
}
答案 6 :(得分:0)
您在评论中提到,id和代码之间的关系不容易猜到。为此你基本上需要加密;鉴于您最初生成的密钥,有大量加密程序和模块可以为您执行加密。要使用这种方法,我建议将你的id转换为ascii(即表示为base-256,然后将每个base-256数字解释为一个字符),然后运行加密,然后转换加密的ascii(base-256) )到基数36,这样你就得到你的字母数字,然后在基数36表示中使用6个随机选择的位置来获取你的代码。您可以解决冲突,例如通过在发生冲突时选择最近的未使用的6位字母数字代码,并注意在(代码&lt; - &gt; id)表中重新分配的字母数字代码,无论如何都必须维护因为如果你只存储加密id的6个base-36位数,你不能直接解密。