產生有效且唯一的標識符
1. 引言
在本教程中,我們將學習如何使用純Java和內建替代方案(例如UUID )建立唯一的自訂識別碼。
2. 概述
許多現代應用程式需要標識符來完成諸如創建 API 金鑰和生成短連結等任務。為了確保有效性,這些標識符必須滿足業務約束,例如最小或最大長度或合法字元集。最關鍵的約束通常是標識符必須隨機且唯一**。**
接下來,我們將展示如何產生符合下列約束條件的唯一識別碼:
- 其長度必須小於
n字元(其中n是可配置的)。 - 它只能包含英文字母([
az] 或 [AZ])或數字([0-9])。
接下來,我們將介紹一些適用於限制較少情況的內建替代方案。
我們的使用場景是每次產生一個標識符,而不是每次呼叫都會產生一批標識符。
3. 隨機數產生器:一種純 Java 方法
與任何隨機產生器一樣,即使產生重複值的機率很低,也總是有可能出現重複(碰撞)。
因此,在輸出標識符之前,我們必須驗證其唯一性。
我們檢查該字串是否已存在於標識符資料儲存區(例如集合、資料庫或其他持久化儲存)中。如果不存在,則將其保存到儲存區並返回,因為我們知道它是唯一的。否則,我們會產生一個新字串並重複檢查。
3.1. 包裝
雖然我們可以使用java.util.Random套件來產生隨機字串,但java.security package.SecureRandom中的 SecureRandom 類別提供了更可靠的隨機數產生器。它提供了一種加密強度高、非確定性的隨機數源,這對於產生無法猜測的 ID 至關重要。
3.2 字串生成
我們在主類別UniqueIdGenerator中定義來源字元集及其限制:
public class UniqueIdGenerator {
private static final String ALPHANUMERIC_CHARACTERS =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
private static final SecureRandom random = new SecureRandom();
private static final idLength = 8; //Default to be overridden
public void setIdLength(int idLength) {}
public String generateUniqueId(Set<String> existingIds) {}
private String generateRandomString(int length) {}
}
generateUniqueId()方法傳回指定長度的唯一隨機識別碼。在內部, generateUniqueId()呼叫了**一個輔助方法generateRandomString() ,該方法使用Java 8 Stream API,並利用SecureRandom從該字串中隨機抽取字元:**
public String generateRandomString(int length) {
return random.ints(length, 0, ALPHANUMERIC_CHARACTERS.length())
.mapToObj(ALPHANUMERIC_CHARACTERS::charAt)
.map(Object::toString)
.collect(Collectors.joining());
}
我們使用random.ints(length, 0, ALPHANUMERIC_CHARACTERS.length())建立一個長度為length的IntStream ,其中包含範圍[0, the length of our character set (exclusive)]隨機整數。然後, mapToObj(ALPHANUMERIC_CHARACTERS::charAt)將每個隨機整數對應到 ` ALPHANUMERIC_CHARACTERS字串中對應索引處的字元。接下來, map(Object::toString)將每個字元轉換為字串。最後,我們使用collect(Collectors.joining())將所有單字元字串連接成一個最終字串。
3.3 獨特性
generateUniqueId()方法使用do-while循環,因為我們需要確保至少嘗試產生一次識別碼。首先,它呼叫generateRandomString().然後,它會檢查existingIds集合中是否已存在該字串。如果已存在,則循環重複。否則,我們知道它是唯一的,並返回它。
public String generateUniqueId(Set<String> existingIds) {
String newId;
do {
newId = generateRandomString(this.idLength);
} while (existingIds.contains(newId));
return newId;
}
4. 其他方法
在其他使用場景中,我們可以使用其他方法。
4.1. 透過 UUID 產生隨機字串
Java提供了一種使用java.util.UUID套件產生唯一識別碼的標準方法:
String uniqueId = UUID.randomUUID().toString().replace("-", "");
這些字串中包含連字號。我們透過將它們替換為空字串來刪除它們。
這樣,我們就能得到一個碰撞機率最小的唯一識別碼。根據IETF RFC 4122 標準,基於 UUID 的識別碼在產生 2.71 兆億 (2.71 × 10¹⁸) 個 UUID 後,發生一次碰撞的機率為 50%。
此外,該方法是線程安全的。但是,傳回的字串包含 36 個字元(包括連字號)。這使得該方法不太適用於較短的 ID。
4.2. 基於時間戳記的標識符
對於需要唯一順序識別碼的系統,我們可以使用底層硬體 CPU 時脈提供的目前系統時間。我們可以透過AtomicLong實現線程安全:
private static final AtomicLong currTime = new AtomicLong(System.currentTimeMillis());
public String generateTimestampId() {
return Long.toString(currTime.incrementAndGet(), Character.MAX_RADIX);
}
5. 性能
下表記錄了在一台配備 16 GB 記憶體的四核心 MacBook Pro 機器上產生 1,000,000 個識別碼所需的時間:
| 方法 | 平均時間(毫秒) |
|---|---|
| 風俗 | 300 |
| 時間戳 | 20 |
| UUID | 600 |
在我們的實驗中,基於時間戳記的方法速度最快。原因是它利用系統時鐘,幾乎沒有額外開銷。另一方面,基於 UUID 的方法速度最慢。
6. 結論
在本文中,我們探討了Java中產生隨機標識符的各種方法.
雖然 UUID 是最安全的預設方法,但對於具有自訂限制的短標識符,自訂生成器更為合適。對於性能至關重要的嵌入式和即時系統,基於時間戳的原子計數器是最有效的。
與往常一樣,本文的完整程式碼可在 GitHub 上找到。