【问题标题】:How to get a unique alphanumeric based on a unique integer如何根据唯一整数获得唯一的字母数字
【发布时间】:2013-07-14 22:44:35
【问题描述】:

我的 web 应用程序在数据库中有一个带有 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);
}

【问题讨论】:

  • id需要随机吗?使用(非随机)计数器更有效 - 您将保证唯一性,而无需检查该值是否已存在
  • 我猜你的意思是代码?它们需要看起来有点随机,所以不容易被猜到。
  • 为什么?为什么不每次需要时都从 ID 计算它?您这样做是在对数据库进行非规范化。
  • 那么,您希望它至少有 6 个字符长,但它可以更长吗?

标签: java algorithm uniqueidentifier


【解决方案1】:

由于要求短代码不可是可猜测的,因此您不想将其与您的 uniqueID 行 ID 绑定。否则,这意味着除了唯一之外,您的 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);
}

【讨论】:

  • 你是对的。我不想要 0000001...0000006 等。所以我在问题中显示的代码可以吗?
  • 太棒了。我需要省略 nextString 中的符号
【解决方案2】:

我会这样做:

String s = Integer.toString(i, 36).toUpperCase();

选择 base-36 将使用字符 0-9a-z 作为数字。要获取使用大写字母的字符串(根据您的问题),您需要将结果折叠为大写。

如果您为您的 id 使用自动递增列,请将下一个值设置为至少 60,466,176,当呈现为基数 36 时为 100000 - 始终为您提供 6 位数字。

【讨论】:

  • 在这种情况下我是数据库生成的id?
  • 是的,我正要添加它
【解决方案3】:

我会从 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);

这将为您带来独特性,无需担心重复。 (也不是随机的)

【讨论】:

  • 这将是理想的,但在某些情况下它不会给出长度为 6 的代码。例如,如果最大 ID 为 3,那么 Integer.toString(3, 36) 只会返回 3
  • 如果需要可以加padding,加个例子也可以从36开始^^ 5
  • 这可行,但也有一些缺点。这个应用程序不会水平扩展,而且简码很容易猜到,因为它是一个单调递增的值。我知道 OP 没有提到可扩展性,但在 cmets 中,他确实希望代码是随机的。
  • 我讨厌改变这个问题。但是当我解决这个问题时,我意识到了更多。我希望每个代码都是字母数字。转换为 base 36 有时会给我 000003 等。
  • @Anthony 000003 是字母数字。你的意思是你想要至少一个字母和至少一个数字?
【解决方案4】:

很简单,您可以使用Integer.toString(int i, int radix)。由于您的基数为 36(26 个字母 + 10 位数字),因此您将基数设置为 36,将 i 设置为整数。例如,要使用16501,请执行以下操作:

 String identifier=Integer.toString(16501, 36);

你可以用.toUpperCase()大写

现在谈谈您的其他问题,是的,您应该先查询数据库以确保它不存在。如果依赖于数据库,它可能需要同步,或者可能不需要同步,因为它将使用自己的锁定系统。在任何情况下,您都需要告诉我们哪个数据库。

关于是否有内置的问题,我们还需要知道数据库类型。

【讨论】:

    【解决方案5】:

    要创建一个随机但唯一的在一个小范围内以下是我知道的一些想法:

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

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

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

      这允许在其他时间(可能在 cron 作业期间)完成查找“空闲 ID”的工作,以便在插入期间不会违反约束,在整个使用过程中保持插入本身的“相同速度”的域。确保使用事务。

    3. 创建一个1-to-1/injective“mixer”函数,使输出“看起来随机”。关键是这个函数必须是一对一的,以避免重复。

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

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

    要查找空闲 ID,上面的方法 #1 和 #2 可以使用“使用 IN 进行探测”来最大限度地减少使用的 SQL 语句的数量。也就是说,生成一个 bunch 随机值并使用 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 编码,这些行会有所改变。这可能会进一步改进以“让它看起来更随机”。

    【讨论】:

      【解决方案6】:

      为了高效使用,请尝试在您的应用程序中将生成的代码缓存在 HashSet&lt;String&gt; 中:

      HashSet<String> codes = new HashSet<String>();
      

      这样您就不必每次都调用 db 来检查生成的代码是否唯一。您所要做的就是:

      codes.contains(newCode);
      

      是的,您应该同步更新缓存的方法

      public synchronize String getCode ()
      {
          String newCode = "";
          do {
              newCode = nextString();
          }
          while(codes.contains(newCode));
          codes.put(newCode);
      }
      

      【讨论】:

        【解决方案7】:

        您在您的 cmets 中提到 id 和 code 之间的关系不应轻易猜测。为此,您基本上需要加密;给定您最初生成的密钥,有很多加密程序和模块可以为您执行加密。要采用这种方法,我建议将您的 id 转换为 ascii(即表示为 base-256,然后将每个 base-256 数字解释为一个字符)然后运行加密,然后转换加密的 ascii(base-256 ) 到 base 36 中,这样你就得到了你的字母数字,然后在 base 36 表示中使用 6 个随机选择的位置来获取你的代码。您可以解决冲突,例如只需在发生碰撞时选择最近的未使用的 6 位字母数字代码,并记下(代码 id)表中为 id 重新分配的字母数字代码,因为您无论如何都必须维护如果只存储 6 个 base-36 位的加密 id,则无法直接解密。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-28
          • 1970-01-01
          • 1970-01-01
          • 2023-03-06
          相关资源
          最近更新 更多