如何基于唯一整数获得唯一的字母数字

时间:2013-07-14 22:44:36

标签: java algorithm uniqueidentifier

我的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);
}

7 个答案:

答案 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)

要在小范围内创建随机但唯一的 ,这里有一些我所知道的想法:

  1. 创建一个新的随机值并尝试插入。

    让数据库约束捕获违规。此列也应该被编入索引。可能需要多次尝试DML,直到找到唯一ID。如上所述,这会随着时间的推移而导致更多的冲突(参见birthday problem)。

  2. 提前创建“免费ID”表,并在使用时将ID标记为正在使用(或从“免费ID”表中删除)。这类似于#1,但在工作完成后会发生变化。

    这允许在另一个时间(可能是在cron作业期间)完成查找“免费ID”的工作,这样在插入过程中不会发生约束违规,使插入本身在整个使用过程中保持“相同的速度”所述域名。确保使用交易。

  3. 创建一个1-to-1/injective“混音器”功能,使输出“显示为随机”。重点是此功能必须是1比1才能避免重复。

    此输出数将是“基数36编码”(这也是单射的);但只要输入(例如,自动增量PK)是唯一的,它就会保证唯一。这可能不如其他方法随机,但仍应创建一个漂亮的非线性输出。

    可以相当简单地在8位查找表周围创建自定义内射函数 - 一次只处理一个字节并适当地改变映射。 我真的很喜欢这个想法,但它仍然可以带来一些可预测的输出

  4. 要查找空闲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位数,你不能直接解密。