2013-07-14 66 views
3

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

ids需要是随机的吗?使用(非随机)计数器会更有效 - 您将保证唯一性,而无需检查值是否已存在 –

+0

我认为您的意思是代码?他们需要看起来有点随意,所以他们不容易猜测。 – Anthony

+0

为什么?为什么不在每次需要时从ID中计算它?通过这样做你可以解除数据库的正常化。 – EJP

回答

3

不想将其绑定到您的唯一ID行ID。否则,这意味着您的rowID除了唯一之外,还需要是随机的。以计数器0开始,并递增,当代码为000001,000002,000003等时,这一点非常明显。

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

您尚未明确地调用可伸缩性,但我认为了解您的设计和扩展的局限性很重要。

在2^31个可能的6夏亚base36值,你将有碰撞中〜65K行(见Birthday Paradox questions

从您的意见,修改代码:

public String nextString() 
{ 
    return Integer.toString(random.nextInt(),36); 
} 
+0

你说得对。我不想要0000001 ... 0000006等。所以我在我的问题中显示的代码可以吗? – Anthony

+0

太好了。我需要省略'nextString'的符号 – Anthony

0

看到简单的,你可以使用Integer.toString(int i, int radix)产生。由于您的基数为36(26个字母+10个数字),因此您将基数设置为36和i为您的整数。例如,使用16501,做到:

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

你可以用.toUpperCase()

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

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

+0

我在这种情况下使用MySQL – Anthony

2

我只想做到这一点:

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

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

如果您为自己的ID使用自动增量列,请将下一个值设置为至少为60,466,176,该值在呈现为基数36时为100000 - 总是给您一个6位数字。

+0

我是数据库生成的ID? – Anthony

+0

是的,我正要在 – Bohemian

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

这会给你唯一性和没有重复担心。 (这不是随机要么)

+0

中添加这个,这将是理想的,但在某些情况下,它不会给出长度为6的代码。例如,如果max ID是3,那么'Integer.toString(3,36)'只返回' 3'。 – Anthony

+1

你可以添加填充,如果你需要它,增加一个例子,或者你可以从36开始^^ 5 –

+1

这个工作,但是有一些缺点。这个应用程序不会横向扩展,而且短代码很容易被猜出,因为它是一个单调递增的值。我知道OP没有提到可扩展性,但在评论中,他确实希望代码是随机的。 – Alan

1

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

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

    让数据库约束捕获违规。这一栏也应该可以编入索引。 DML可能需要尝试几次,直到找到唯一的ID。正如所指出的那样,随着时间的推移,这将导致更多的碰撞(请参阅birthday problem)。

  2. 提前创建一个“空闲ID”表,并根据使用情况将该ID标记为正在使用(或将其从“空闲ID”表中删除)。这与#1类似,但是在完成工作时转移。

    这使得可以在另一个时间(可能在cron作业期间)完成查找“自由ID”的工作,以便在插入过程中保持插入本身的“相同速度”不会违反约束条件所述域的使用。确保使用交易。

  3. 创建一个1-to-1/injective“调音台”功能,使输出“随机显示”。关键是这个函数必须是1对1来固有地避免重复。

    这个输出数字然后是“基地36编码”(这也是内射的);但只要输入(比如自动递增PK)是唯一的,它就会被保证是唯一的。这可能不像其他方法那样随机,但仍应该创建一个漂亮的非线性输出。

    一个自定义的内射函数可以在8位查找表的周围创建 - 很简单,一次只处理一个字节,然后适当地洗牌。 我真的很喜欢这个主意,但它仍然可以导致在一定程度上预测的输出

找到免费的标识,方法#1和#2以上可以使用“在探索”以减少SQL语句的数量用过的。也就是说,生成一个的随机值,然后使用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编码,行会稍微改变一点。这可能会进一步改善,使其“看起来更随机”。

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

您在您的评论中提到id和代码之间的关系不应该很容易被猜出。为此,你基本上需要加密;有大量的加密程序和模块可以为您执行加密,只要给出您最初生成的密钥。为了使用这种方法,我建议将你的id转换为ascii(即,以256为底数,然后将每个base-256数字解释为一个字符),然后运行加密,然后将加密的ascii(base-256 )到基地36,所以你得到你的字母数字,然后在基地36表示中使用6个随机选择的位置来获得你的代码。您可以解决冲突,例如通过在碰撞发生时选择最近未使用的6位数字字母数字代码,并注意在(代码< - > id)表格中为该id重新分配的字母数字代码,因此您必须始终保持该代码如果只存储加密标识的6个基数 - 36位数字,则无法直接解密。

相关问题