在 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 上取得。