3

我的 web 应用程序在数据库中有一个表,其中有一id列对于每一行总是唯一的。除此之外,我还希望有另一个名为的列code,它将有一个 6 位唯一的字母数字代码,数字为 0-9 和字母 AZ。字母和数字可以在代码中重复。IEFFQ77J。我知道随着时间的推移,随着更多行的添加,这个 6 位字母数字代码的唯一性会降低,但现在我可以接受。

要求(更新) - 代码长度至少应为 6 - 每个代码应为字母数字

所以我想生成这个字母数字代码。

问题

有什么好方法可以做到这一点?

  • 我是否应该生成代码并在生成之后对数据库运行查询并检查它是否已经存在,如果存在则生成一个新的?为了保证唯一性,这段代码是否需要同步,只有一个线程运行?
  • 数据库中是否有内置的东西可以让我这样做?

对于这一代,我将使用我在这个答案中看到的类似的东西

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);
}
4

7 回答 7

3

我会这样做:

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

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

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

于 2013-07-14T22:47:19.333 回答
3

由于要求短代码不可猜测,因此您不想将其与您的 uniqueID 行 ID 绑定。否则,这意味着除了唯一之外,您的 rowID 还需要是随机的。从计数器 0 开始并递增,当您的代码为:000001、000002、000003 等时,这一点非常明显。

对于您的短代码,生成一个随机的 32 位 int,省略符号并转换为 base36。调用您的数据库,以确保它可用。

您没有明确指出可扩展性,但我认为了解设计对可扩展性的限制很重要。

在 2^31 个可能的 6 char base36 值处,您将在 ~65k 行发生冲突(请参阅生日悖论问题

根据您的评论,修改您的代码:

public String nextString()
{
    return Integer.toString(random.nextInt(),36);
}
于 2013-07-14T23:06:13.323 回答
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);

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

于 2013-07-14T22:50:57.570 回答
1

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

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

你可以用大写.toUpperCase()

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

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

于 2013-07-14T22:46:51.793 回答
1

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

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

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

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

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

  3. 创建一个1 对 1/内射“混合器”函数,使输出“出现随机”。关键是这个函数必须是一对一的,以避免重复。

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

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

为了找到空闲的 ID,上面的方法 #1 和 #2 可以使用“probing with 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 编码,这些行会有所改变。这可能会进一步改进以“让它看起来更随机”。

于 2013-07-14T22:57:09.373 回答
0

您在评论中提到 id 和 code 之间的关系不应轻易猜测。为此,您基本上需要加密;给定您最初生成的密钥,有很多加密程序和模块可以为您执行加密。要采用这种方法,我建议将您的 id 转换为 ascii(即表示为 base-256,然后将每个 base-256 数字解释为一个字符)然后运行加密,然后转换加密的 ascii(base-256 ) 转换为基数 36,以便您获得字母数字,然后在基数 36 表示中使用 6 个随机选择的位置来获取您的代码。您可以解决冲突,例如,当发生冲突时,只需选择最近的未使用的 6 位字母数字代码,

于 2013-07-14T23:24:44.923 回答
0

为了有效使用,请尝试在您的应用程序中缓存生成的代码HashSet<String>

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);
}
于 2013-07-14T23:01:07.713 回答