在 Java 中使用 UUID 產生唯一正長整數
1. 概述
UUID(通用唯一識別碼)表示一個 128 位數,旨在全球唯一。實際上,UUID適用於需要唯一識別的情況,例如為資料庫表建立主鍵。
在Java中,原始long是一種易於人類閱讀和理解的資料類型。在許多情況下,64 位long的唯一性通常就足夠了,並且發生衝突的機率相當低。此外,MySQL、PostgreSQL 等資料庫經過最佳化,可以有效地處理數位資料類型。
在本文中,我們將討論使用 Java UUID類別產生唯一long值的各種方法。
2. 生成唯一正long
這是一個有趣的挑戰,因為 UUID 表示為 128 位元值,但long只有 64 位元。這意味著long不會像 UUID 那樣唯一。但是,我們將嘗試各種方法來克服這個問題。
在本例中,我們將首先建立一個類別UUIDPositiveLongGenerator來容納這些內容並使以後的測試更容易:
public class UUIDPositiveLongGenerator {
// methods to generate a unique positive long using a UUID
}
2.1.使用getLeastSignificantBits()
getLeastSignificantBits()是UUID類別內建的方法,它傳回 UUID 的最低 64 位元。這意味著它僅提供(準確地說)128 位元 UUID 值的二分之一。
讓我們在呼叫randomUUID()方法後在 Java 中呼叫它:
public long getLeastSignificantBits(){
return Math.abs(UUID.randomUUID().getLeastSignificantBits());
}
我們將繼續在以下每個方法中使用Math.abs()以確保正值。
2.2.使用getMostSignificantBits()
同樣, UUID類別中的getMostSignificantBits()方法也傳回 64 位元long 。不同之處在於,它採用 128 位元 UUID 值中位於最高位置的位元。
同樣,我們將其連結在randomUUID()方法之後:
public long getMostSignificantBits(){
return Math.abs(UUID.randomUUID().getMostSignificantBits());
}
正如我們所看到的,如果簡單性和速度是我們的首要任務,那麼選擇getLeastSignificantBits()或getMostSignificantBits()是一個很好的選擇。
2.3.使用ByteBuffer組合
或者,我們可以透過組合 UUID 中的getMostSignificantBits()和getLeastSignificantBits()來增強結果值的唯一性和分佈。
這次,我們將使用ByteBuffer來組合它們:
public long combineByteBuffer(){
UUID uuid = UUID.randomUUID();
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
bb.rewind();
return Math.abs(bb.getLong());
}
在這裡,我們使用 16 位元組或 128 位元的ByteBuffer ,因為這個大小足以容納兩個long值。然後我們呼叫rewind()將緩衝區位置設定回開頭。接下來,我們呼叫getLong()來取得組合值。
這樣,我們希望產生更多唯一性和隨機性的值,儘管我們仍然不能保證完全唯一性。
由於它使用ByteBuffer ,因此此方法可能會比僅使用getMostSignificantBits()或getLeastSignificantBits()產生更多的記憶體分配。
2.4.使用位元運算組合
我們也可以使用位元運算將getLeastSignificantBits()和getMostSignificantBits()結合:
public long combineBitwise() {
UUID uniqueUUID = UUID.randomUUID();
long mostSignificantBits = uniqueUUID.getMostSignificantBits();
long leastSignificantBits = uniqueUUID.getLeastSignificantBits();
return Math.abs((mostSignificantBits << 32) | (leastSignificantBits & 0xFFFFFFFFL));
}
首先,我們取出最高有效位的最後 32 位元並將它們移到左側。然後,我們取出最低有效位的最後 32 位元並將它們保留在右側。結果是兩者的組合,其中填充了long的所有 64 位元。
這種方式在效能方面優於ByteBuffer (僅使用位元運算) ,儘管兩者仍有衝突的可能性。
這種方法所需的額外記憶體不像ByteBuffer那樣廣泛。
2.5.直接組合位
我們也可以透過直接組合這些位元來做到這一點:
public long combineDirect(){
UUID uniqueUUID = UUID.randomUUID();
long mostSignificantBits = uniqueUUID.getMostSignificantBits();
long leastSignificantBits = uniqueUUID.getLeastSignificantBits();
return Math.abs(mostSignificantBits ^ (leastSignificantBits >> 1));
}
我們將leastSignificantBits向右移動一位,這樣最右邊的位將被刪除,並在左側加上一個零位。
然後,我們使用 XOR 運算子 (^) 將mostSignificantBits的結果與leastSignificantBits移位的結果組合起來。
在這種方法中,記憶體分配比以前的方法更有效,因為操作更簡單,儘管仍然存在衝突的可能性。
2.6。使用簡單排列組合
我們還可以對UUID位元組執行簡單的排列以創建更大的變化並將它們轉換為long值:
public long combinePermutation(){
UUID uuid = UUID.randomUUID();
long mostSigBits = uuid.getMostSignificantBits();
long leastSigBits = uuid.getLeastSignificantBits();
byte[] uuidBytes = new byte[16];
for (int i = 0; i < 8; i++) {
uuidBytes[i] = (byte) (mostSigBits >>> (8 * (7 - i)));
uuidBytes[i + 8] = (byte) (leastSigBits >>> (8 * (7 - i)));
}
long result = 0;
for (byte b : uuidBytes) {
result = (result << 8) | (b & 0xFF);
}
return Math.abs(result);
}
我們將UUID轉換為byte數組,然後將byte數組轉換為long值。
這種方法需要為byte array迭代分配額外的內存,並且需要更多指令,因為它需要將位元移動到位元組中。
這比以前的方法稍微長一些,但透過這種方法,我們可以更好地控制 UUID 形成過程。
3. 比較
讓我們根據四個主要標準來比較我們提出的方法:簡單性、執行速度、記憶體使用效率以及生成值的唯一性等級:
| 方法 | 簡單 | 執行速度 | 記憶體使用效率 | 獨特性水平 |
|---|---|---|---|---|
getLeastSignificantBits() |
4 | 4 | 5 | 3 |
getMostSignificantBits() |
4 | 4 | 5 | 3 |
combineByteBuffer() |
3 | 2 | 2 | 4 |
combineBitwise() |
3 | 3 | 3 | 4 |
combineDirect() |
3 | 3 | 3 | 4 |
combinePermutation() |
2 | 3 | 3 | 4 |
在這種情況下,分數以 1 到 5 的範圍給出,其中較高的值表示給定標準中的更好的性能或特徵。
4。結論
在本文中,我們探索了使用 UUID 產生唯一正長值的各種方法。方法的選擇最終取決於特定的用例要求。如果簡單性和速度至關重要,那麼人們可能更喜歡直接存取方法。另一方面,如果優先考慮唯一性和可變性,則按位和基於排列的方法可提供更多樣化的結果。
在實際專案(例如資料庫)中,透過使用主鍵、實施唯一約束、在頻繁存取的列上建立索引以及採用事務和鎖定技術,可以更容易地維護唯一性和防止衝突。因此,我們必須確保選擇符合我們特定需求的獨特策略。
與往常一樣,完整的源代碼可以在 GitHub 上取得。