從唯一字串產生唯一整數
1.概述
在這篇快速文章中,我們將探討從唯一的String
產生唯一的Integer
的可能性。雖然 Java 提供了多種方法來實現這一點,但每種方法在速度、簡單性和獨特性方面都進行了不同的平衡。
2. 獨特意味著什麼?
唯一性意味著不同的String
映射到不同的int
,理想情況下不會發生衝突。但是,由於int
只有 2^32 個可能的值,因此在對許多字串進行雜湊處理時可能會發生衝突。
唯一性不是二進制的-像hashCode()
這樣的方法提供了機率唯一性,但很少發生衝突,而查找圖則保證了這一點。
每個解決方案都應該考慮我們的輸入空間有多大,以及輸出中允許有多少碰撞(如果有的話)。
2.1.驗證
為了確保我們的實作能如預期運行,我們使用參數化的 JUnit 測試來測試它們的唯一性:
private static Stream<Arguments> implementations() {
return Stream.of(Arguments.of(Named.<Function<String, Integer>> of("toIntByHashCode", StringToUniqueInt::toIntByHashCode)),
Arguments.of(Named.<Function<String, Integer>> of("toIntByCR32", StringToUniqueInt::toIntByCR32)),
Arguments.of(Named.<Function<String, Integer>> of("toIntByCharFormula", StringToUniqueInt::toIntByCharFormula)),
Arguments.of(Named.<Function<String, Integer>> of("toIntByMD5", StringToUniqueInt::toIntByMD5)),
Arguments.of(Named.<Function<String, Integer>> of("toIntByLookup", StringToUniqueInt::toIntByLookup))
);
}
@ParameterizedTest
@MethodSource("implementations")
public void given1kElements_whenMappedToInt_thenItShouldHaveNoDuplicates(Function<String, Integer> implementation) {
Stream<String> strings = uniqueStringsOfSize(1_000); // may be increased for better guarantees
List<Integer> integers = strings.map(implementation)
.toList();
assertThat(integers).doesNotHaveDuplicates();
}
對於我們提供的每個實現,測試都會產生大量唯一的String
Set
,並將每個值對應到一個Integer
。斷言檢查結果List
中是否有重複項,以驗證解決方案的有效性。
3.解決方案
讓我們深入研究從String
產生唯一int
的五種實用方法。
3.1.使用String.hashCode()
我們的第一個解決方案可能是最明顯的,使用hashCode()
:
public static int toIntByHashCode(String value) {
return value.hashCode();
}
它速度很快並且內建於 Java,非常適合快速快取或非關鍵應用程式。但是, String.hashCode()
並非無衝突,因為多個字串可以產生相同的int
。
當速度比保證唯一性更重要時,我們可以使用它。
3.2.使用有公式的字符
為了更好地控制,我們可以製定一個應用於每個角色的自訂公式:
public static int toIntByCharFormula(String value) {
return value.chars()
.reduce(17, (a, b) -> a * 13 + (b / (a + 1))); // or any other equation
}
它簡單且可自訂但容易發生衝突,類似於hashCode()
。它適用於教育目的或當我們需要定制的哈希函數時,但我們應該徹底測試碰撞風險。
3.3.使用 CRC32 進行校驗
第三種也是最可靠的方法是使用來自java.util.zip:
public static int toIntByCR32(String value) {
CRC32 crc32 = new CRC32();
crc32.update(value.getBytes());
return (int) crc32.getValue();
}
CRC32 處理字串的位元組以產生 32 位元校驗和,並將其轉換為int
。它專為錯誤檢測而設計,提供比hashCode()
更低的碰撞機率。
雖然速度較慢,但對於文件索引或資料完整性檢查等以穩健性為關鍵的應用程式來說,它是可靠的。
3.4.使用位元組移位的 MD5
對於加密方法,我們使用 MD5 雜湊:
public static int toIntByMD5(String value) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] hash = digest.digest(value.getBytes());
return ((hash[0] & 0xFF) << 24) | ((hash[1] & 0xFF) << 16)
| ((hash[2] & 0xFF) << 8) | (hash[3] & 0xFF);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 not supported", e);
}
}
MD5 產生一個 128 位元雜湊值,我們從中提取前四個位元組,使用位元運算形成一個 32 位元int
。
它速度較慢,但碰撞風險非常低,因此適用於唯一密鑰產生等高可靠性場景。然而,對於簡單的用例來說,這可能有點過度了。
3.5. 使用 Lookup
當必須保證唯一性時,我們的最後一種方法很有用。
我們使用HashMap
作為簡單的持久層來儲存產生的整數及其字串:
private static final Map<String, Integer> lookupMap = new HashMap<>();
private static final AtomicInteger counter = new AtomicInteger(Integer.MIN_VALUE);
在這種情況下,實作非常簡單——要么為特定的 S tring
返回一個已經生成的int
,要么為新的Integer
增加counter
並儲存它:
public static int toIntByLookup(String value) {
var found = lookupMap.get(value);
if (found != null) {
return found;
}
var intValue = counter.incrementAndGet();
lookupMap.put(value, intValue);
return intValue;
}
它透過使用AtomicInteger
計數器來保證唯一性,非常適合資料庫鍵或持久性識別碼。代價是記憶體使用量,它會隨著字串數量的增加而增加。
4. 結論
在 Java 中,有多種方法可以從String
產生唯一的int
,每種方法都有不同的權衡。
hashCode()
和自訂公式速度很快,但有衝突的風險,適合快取。 CRC32 和 MD5 為可靠索引提供了強大、低衝突的選項。查找圖以記憶體為代價確保唯一性,非常適合關鍵應用程式。我們應該根據速度、可靠性或可擴展性的需求進行選擇。
與往常一樣,本文使用的完整程式碼可以在 GitHub 上找到。