可笑,你竟然不知道 Java 如何生成 UUID
一個調皮的讀者在之前我寫的“我去”系列文章裏留言調侃說,“二哥,你是無中生小王嗎?”不不不,其實真不是的,小王是真實存在的,他一直和我並肩作戰,不辭辛勞,讓我既愛又恨。我愛他,因爲他兢兢業業,任勞任怨,和我心有靈犀;我恨他,因爲他時不時會中二一下,問我一些可笑的問題,比如說這次,“二哥,你能給我說說 Java 如何生成 UUID 嗎?”
UUID,全名叫做 Universally Unique Identifier,也就是通用唯一標識符的意思。有時候,也叫做全局唯一標識符,英文全名叫做 Globally Unique Identifier,簡拼爲 GUID。
來看一下 UUID 的格式:
123e4567-e89b-12d3-a456-556642440000 xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
由四個中劃線“-”隔開,第一部分的長度爲 8,第二部分和第三部分的長度爲 4,第四部分的長度爲 12,總長度爲 36,是固定的。每一部分都是一個十六進制的數字,注意並不是隨機的任意字母+數字的字符串。
M 表示 UUID 的版本,N 爲 UUID 的變體(Variants)。
M 的值有 5 個可選項:
-
版本 1:UUID 是根據時間和 MAC 地址生成的;
-
版本 2:UUID 是根據標識符(通常是組或用戶 ID)、時間和節點 ID生成的;
-
版本 3:UUID 是通過散列(MD5 作爲散列算法)名字空間(namespace)標識符和名稱生成的;
-
版本 4 - UUID 使用隨機性或僞隨機性生成;
-
版本 5 類似於版本 3(SHA1 作爲散列算法)。
爲了能兼容過去的 UUID,以及應對未來的變化,因此有了變體(Variants)這一概念。
目前已知的變體有下面 4 種:
-
變體 0:格式爲 0xxx,爲了向後兼容預留。
-
變體 1:格式爲 10xx,當前正在使用的。
-
變體 2:格式爲 11xx,爲早期微軟的 GUID 預留。
-
變體 3:格式爲 111x,爲將來的擴展預留,目前暫未使用。
在上例中,M 是 1,N 是 a(二進制爲 1010,符合 10xx 的格式),這就意味着這個 UUID 是“版本 1”、“變體 1”的 UUID。
目前大多數使用的 UUID 大都是變體 1,N 的取值是 8、9、a、b 中的一個。
System.out.println(Integer.toBinaryString(Integer.valueOf("8",16))); // 1000 System.out.println(Integer.toBinaryString(Integer.valueOf("a",16))); // 1010 System.out.println(Integer.toBinaryString(Integer.valueOf("9",16))); // 1001 System.out.println(Integer.toBinaryString(Integer.valueOf("b",16))); // 1011
8 的二進制爲 1000
,9 的二進制爲 1001
,a 的二進制爲 1010
,b 的二進制爲 1011
,都符合 10xx
的格式。
由於 UUID 是全局唯一的,重複 UUID 的概率接近零,可以忽略不計。所以 Java 的 UUID 通常可用於以下地方:
-
隨機生成的文件名;
-
Java Web 應用程序的 sessionID;
-
數據庫表的主鍵;
-
事務 ID(UUID 生成算法非常高效,每臺計算機每秒高達 1000 萬次)。
在 Java 中,就有一個叫 UUID 的類,在 java.util
包下。
package java.util; public final class UUID implements java.io.Serializable, Comparable<UUID> { }
該類只有一個構造方法:
public UUID(long mostSigBits, long leastSigBits) { this.mostSigBits = mostSigBits; this.leastSigBits = leastSigBits; }
要使用構造方法創建 UUID 對象的話,就需要傳遞兩個參數,long 型的最高位 UUID 和最低位的 UUID。
long msb = System.currentTimeMillis(); long lsb = System.currentTimeMillis(); UUID uuidConstructor = new UUID(msb, lsb); System.out.println("UUID : "+uuidConstructor);
輸出結果如下所示:
UUID : 00000173-8efd-1b7c-0000-01738efd1b7c
UUID 類提供了一個靜態方法 randomUUID()
:
public static UUID randomUUID() { SecureRandom ng = UUID.Holder.numberGenerator; byte[] randomBytes = new byte[16]; ng.nextBytes(randomBytes); randomBytes[6] &= 0x0f; /* clear version */ randomBytes[6] |= 0x40; /* set to version 4 */ randomBytes[8] &= 0x3f; /* clear variant */ randomBytes[8] |= 0x80; /* set to IETF variant */ return new UUID(randomBytes); }
randomUUID()
方法生成了一個版本 4 的 UUID,這也是生成 UUID 最方便的方法。如果只使用原生 JDK 的話,基本上都用的這種方式。
示例如下:
UUID uuid4 = UUID.randomUUID(); int version4 = uuid4.version(); System.out.println("UUID:"+ uuid4+" 版本 " + version4);
程序輸出結果如下所示:
UUID:8c943921-d83e-424a-a627-a12d3cb474db 版本 4
除此之外,UUID 類還提供了另外兩個靜態方法,其一是 nameUUIDFromBytes()
:
public static UUID nameUUIDFromBytes(byte[] name) { MessageDigest md; try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException nsae) { throw new InternalError("MD5 not supported", nsae); } byte[] md5Bytes = md.digest(name); md5Bytes[6] &= 0x0f; /* clear version */ md5Bytes[6] |= 0x30; /* set to version 3 */ md5Bytes[8] &= 0x3f; /* clear variant */ md5Bytes[8] |= 0x80; /* set to IETF variant */ return new UUID(md5Bytes); }
nameUUIDFromBytes()
會生成一個版本 3 的 UUID,不過需要傳遞一個名稱的字節數組作爲參數。
示例如下:
UUID uuid3 = UUID.nameUUIDFromBytes("test".getBytes()); int version3 = uuid3.version(); System.out.println("UUID:"+ uuid3+" 版本 " + version3);
程序輸出結果如下所示:
UUID:098f6bcd-4621-3373-8ade-4e832627b4f6 版本 3
其二是 fromString()
:
public static UUID fromString(String name) { int len = name.length(); if (len > 36) { throw new IllegalArgumentException("UUID string too large"); } int dash1 = name.indexOf('-', 0); int dash2 = name.indexOf('-', dash1 + 1); int dash3 = name.indexOf('-', dash2 + 1); int dash4 = name.indexOf('-', dash3 + 1); int dash5 = name.indexOf('-', dash4 + 1); // For any valid input, dash1 through dash4 will be positive and dash5 // negative, but it's enough to check dash4 and dash5: // - if dash1 is -1, dash4 will be -1 // - if dash1 is positive but dash2 is -1, dash4 will be -1 // - if dash1 and dash2 is positive, dash3 will be -1, dash4 will be // positive, but so will dash5 if (dash4 < 0 || dash5 >= 0) { throw new IllegalArgumentException("Invalid UUID string: " + name); } long mostSigBits = Long.parseLong(name, 0, dash1, 16) & 0xffffffffL; mostSigBits <<= 16; mostSigBits |= Long.parseLong(name, dash1 + 1, dash2, 16) & 0xffffL; mostSigBits <<= 16; mostSigBits |= Long.parseLong(name, dash2 + 1, dash3, 16) & 0xffffL; long leastSigBits = Long.parseLong(name, dash3 + 1, dash4, 16) & 0xffffL; leastSigBits <<= 48; leastSigBits |= Long.parseLong(name, dash4 + 1, len, 16) & 0xffffffffffffL; return new UUID(mostSigBits, leastSigBits); }
fromString()
方法會生成一個基於指定 UUID 字符串的 UUID 對象,如果指定的 UUID 字符串不符合 UUID 的格式,將拋出 IllegalArgumentException 異常。
示例如下:
UUID uuid = UUID.fromString("38400000-8cf0-11bd-b23e-10b96e4ef00d"); int version = uuid.version(); System.out.println("UUID:"+ uuid+" 版本 " + version);
程序輸出結果如下所示:
UUID:38400000-8cf0-11bd-b23e-10b96e4ef00d 版本 1
除了使用 JDK 原生的 API 之外,還可以使用 com.fasterxml.uuid.Generators
,需要先在項目中加入該類的 Maven 依賴。
<dependency> <groupId>com.fasterxml.uuid</groupId> <artifactId>java-uuid-generator</artifactId> <version>3.1.4</version> </dependency>
然後我們來看一下如何使用它:
/** * @author 沉默王二,一枚有趣的程序員 */ public class UUIDVersionExample { public static void main(String[] args) { UUID uuid1 = Generators.timeBasedGenerator().generate(); System.out.println("UUID : "+uuid1); System.out.println("UUID 版本 : "+uuid1.version()); UUID uuid2 = Generators.randomBasedGenerator().generate(); System.out.println("UUID : "+uuid2); System.out.println("UUID 版本 : "+uuid2.version()); } }
Generators.timeBasedGenerator().generate()
可用於生成版本 1 的 UUID, Generators.randomBasedGenerator().generate()
可用於生成版本 4 的 UUID。
來看一下輸出結果:
UUID : ebee82f5-cfd2-11ea-82a7-8536e13d4951 UUID 版本 : 1 UUID : d2ccc752-c824-4bbc-8cc7-52c8246bbc6a UUID 版本 : 4
好了,我想關於 UUID 的一切,我都已經說明白了。趕緊把這篇文章先發給小王預覽一下,讓他漲漲見識。
------------------
公衆號:沉默王二(ID:cmower)
CSDN:沉默王二
這是一個有顏值卻靠才華喫飯的程序員,你知道,他的文章風趣幽默,讀起來就好像花錢一樣爽快。
長按下圖二維碼關注,你將感受到一個有趣的靈魂, 且每篇文章都有乾貨。
-------------- --- -
原創不易,莫要白票,如果覺得有點用的話,請毫不留情地素質三連吧,分享、點贊、在看,我不挑 ,因爲這將是我寫作更多優質文章的最強動力。
PS:8 月份第一天,希望小夥伴們在新的一個月份裏都能夠順順利利,加油!