可笑,你竟然不知道 Java 如何生成 UUID

一個調(diào)皮的讀者在之前我寫的“我去”系列文章里留言調(diào)侃說,“二哥,你是無中生小王嗎?”不不不,其實真不是的,小王是真實存在的,他一直和我并肩作戰(zhàn),不辭辛勞,讓我既愛又恨。我愛他,因為他兢兢業(yè)業(yè),任勞任怨,和我心有靈犀;我恨他,因為他時不時會中二一下,問我一些可笑的問題,比如說這次,“二哥,你能給我說說 Java 如何生成 UUID 嗎?”
UUID,全名叫做 Universally Unique Identifier,也就是通用唯一標(biāo)識符的意思。有時候,也叫做全局唯一標(biāo)識符,英文全名叫做 Globally Unique Identifier,簡拼為 GUID。
來看一下 UUID 的格式:
123e4567-e89b-12d3-a456-556642440000
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
由四個中劃線“-”隔開,第一部分的長度為 8,第二部分和第三部分的長度為 4,第四部分的長度為 12,總長度為 36,是固定的。每一部分都是一個十六進(jìn)制的數(shù)字,注意并不是隨機(jī)的任意字母+數(shù)字的字符串。
M 表示 UUID 的版本,N 為 UUID 的變體(Variants)。
M 的值有 5 個可選項:
版本 1:UUID 是根據(jù)時間和 MAC 地址生成的;
版本 2:UUID 是根據(jù)標(biāo)識符(通常是組或用戶 ID)、時間和節(jié)點(diǎn) ID生成的;
版本 3:UUID 是通過散列(MD5 作為散列算法)名字空間(namespace)標(biāo)識符和名稱生成的;
版本 4 - UUID 使用隨機(jī)性或偽隨機(jī)性生成;
版本 5 類似于版本 3(SHA1 作為散列算法)。
為了能兼容過去的 UUID,以及應(yīng)對未來的變化,因此有了變體(Variants)這一概念。
目前已知的變體有下面 4 種:
變體 0:格式為 0xxx,為了向后兼容預(yù)留。
變體 1:格式為 10xx,當(dāng)前正在使用的。
變體 2:格式為 11xx,為早期微軟的 GUID 預(yù)留。
變體 3:格式為 111x,為將來的擴(kuò)展預(yù)留,目前暫未使用。
在上例中,M 是 1,N 是 a(二進(jìn)制為 1010,符合 10xx 的格式),這就意味著這個 UUID 是“版本 1”、“變體 1”的 UUID。
目前大多數(shù)使用的 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 的二進(jìn)制為 1000,9 的二進(jìn)制為 1001,a 的二進(jìn)制為 1010,b 的二進(jìn)制為 1011,都符合 10xx 的格式。
由于 UUID 是全局唯一的,重復(fù) UUID 的概率接近零,可以忽略不計。所以 Java 的 UUID 通??捎糜谝韵碌胤剑?/p>
隨機(jī)生成的文件名;
Java Web 應(yīng)用程序的 sessionID;
數(shù)據(jù)庫表的主鍵;
事務(wù) ID(UUID 生成算法非常高效,每臺計算機(jī)每秒高達(dá) 1000 萬次)。
在 Java 中,就有一個叫 UUID 的類,在 java.util 包下。
package?java.util;
public?final?class?UUID?implements?java.io.Serializable,?Comparable<UUID>?{
}
該類只有一個構(gòu)造方法:
public?UUID(long?mostSigBits,?long?leastSigBits)?{
????this.mostSigBits?=?mostSigBits;
????this.leastSigBits?=?leastSigBits;
}
要使用構(gòu)造方法創(chuàng)建 UUID 對象的話,就需要傳遞兩個參數(shù),long 型的最高位 UUID 和最低位的 UUID。
long?msb?=?System.currentTimeMillis();
long?lsb?=?System.currentTimeMillis();
UUID?uuidConstructor?=?new?UUID(msb,?lsb);
System.out.println("UUID?:?"+uuidConstructor);
輸出結(jié)果如下所示:
UUID?:?00000173-8efd-1b7c-0000-01738efd1b7c
UUID 類提供了一個靜態(tài)方法 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);
程序輸出結(jié)果如下所示:
UUID:8c943921-d83e-424a-a627-a12d3cb474db?版本?4
除此之外,UUID 類還提供了另外兩個靜態(tài)方法,其一是 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,不過需要傳遞一個名稱的字節(jié)數(shù)組作為參數(shù)。
示例如下:
UUID?uuid3?=?UUID.nameUUIDFromBytes("test".getBytes());
int?version3?=?uuid3.version();
System.out.println("UUID:"+?uuid3+"?版本?"?+?version3);
程序輸出結(jié)果如下所示:
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);
程序輸出結(jié)果如下所示:
UUID:38400000-8cf0-11bd-b23e-10b96e4ef00d?版本?1
除了使用 JDK 原生的 API 之外,還可以使用 com.fasterxml.uuid.Generators,需要先在項目中加入該類的 Maven 依賴。
<dependency>
????<groupId>com.fasterxml.uuidgroupId>
????<artifactId>java-uuid-generatorartifactId>
????<version>3.1.4version>
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。
來看一下輸出結(jié)果:
UUID?:?ebee82f5-cfd2-11ea-82a7-8536e13d4951
UUID?版本?:?1
UUID?:?d2ccc752-c824-4bbc-8cc7-52c8246bbc6a
UUID?版本?:?4
好了,我想關(guān)于 UUID 的一切,我都已經(jīng)說明白了。趕緊把這篇文章先發(fā)給小王預(yù)覽一下,讓他漲漲見識。
公眾號:沉默王二(ID:cmower)
CSDN:沉默王二
這是一個有顏值卻靠才華吃飯的程序員,你知道,他的文章風(fēng)趣幽默,讀起來就好像花錢一樣爽快。
長按下圖二維碼關(guān)注,你將感受到一個有趣的靈魂,且每篇文章都有干貨。
------------------
原創(chuàng)不易,莫要白票,如果覺得有點(diǎn)用的話,請毫不留情地素質(zhì)三連吧,分享、點(diǎn)贊、在看,我不挑,因為這將是我寫作更多優(yōu)質(zhì)文章的最強(qiáng)動力。
PS:8 月份第一天,希望小伙伴們在新的一個月份里都能夠順順利利,加油!
