2013-07-14 91 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位數字,則無法直接解密。

相關問題