1. 程式人生 > 實用技巧 >可笑,你竟然不知道 Java 如何生成 UUID

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

先看再點贊,給自己一點思考的時間,微信搜尋【沉默王二】關注這個靠才華苟且的程式設計師。
本文 GitHub github.com/itwanger 已收錄,裡面還有一線大廠整理的面試題,以及我的系列文章。

一個調皮的讀者在之前我寫的“我去”系列文章裡留言調侃說,“二哥,你是無中生小王嗎?”不不不,其實真不是的,小王是真實存在的,他一直和我並肩作戰,不辭辛勞,讓我既愛又恨。我愛他,因為他兢兢業業,任勞任怨,和我心有靈犀;我恨他,因為他時不時會中二一下,問我一些可笑的問題,比如說這次,“二哥,你能給我說說 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 包下。

packagejava.util;
publicfinalclassUUIDimplementsjava.io.Serializable,Comparable<UUID>{
}

該類只有一個構造方法:

publicUUID(longmostSigBits,longleastSigBits){
this.mostSigBits=mostSigBits;
this.leastSigBits=leastSigBits;
}

要使用構造方法建立 UUID 物件的話,就需要傳遞兩個引數,long 型的最高位 UUID 和最低位的 UUID。

longmsb=System.currentTimeMillis();
longlsb=System.currentTimeMillis();
UUIDuuidConstructor=newUUID(msb,lsb);
System.out.println("UUID:"+uuidConstructor);

輸出結果如下所示:

UUID:00000173-8efd-1b7c-0000-01738efd1b7c

UUID 類提供了一個靜態方法 randomUUID()

publicstaticUUIDrandomUUID(){
SecureRandomng=UUID.Holder.numberGenerator;

byte[]randomBytes=newbyte[16];
ng.nextBytes(randomBytes);
randomBytes[6]&=0x0f;/*clearversion*/
randomBytes[6]|=0x40;/*settoversion4*/
randomBytes[8]&=0x3f;/*clearvariant*/
randomBytes[8]|=0x80;/*settoIETFvariant*/
returnnewUUID(randomBytes);
}

randomUUID() 方法生成了一個版本 4 的 UUID,這也是生成 UUID 最方便的方法。如果只使用原生 JDK 的話,基本上都用的這種方式。

示例如下:

UUIDuuid4=UUID.randomUUID();
intversion4=uuid4.version();
System.out.println("UUID:"+uuid4+"版本"+version4);

程式輸出結果如下所示:

UUID:8c943921-d83e-424a-a627-a12d3cb474db版本4

除此之外,UUID 類還提供了另外兩個靜態方法,其一是 nameUUIDFromBytes()

publicstaticUUIDnameUUIDFromBytes(byte[]name){
MessageDigestmd;
try{
md=MessageDigest.getInstance("MD5");
}catch(NoSuchAlgorithmExceptionnsae){
thrownewInternalError("MD5notsupported",nsae);
}
byte[]md5Bytes=md.digest(name);
md5Bytes[6]&=0x0f;/*clearversion*/
md5Bytes[6]|=0x30;/*settoversion3*/
md5Bytes[8]&=0x3f;/*clearvariant*/
md5Bytes[8]|=0x80;/*settoIETFvariant*/
returnnewUUID(md5Bytes);
}

nameUUIDFromBytes() 會生成一個版本 3 的 UUID,不過需要傳遞一個名稱的位元組陣列作為引數。

示例如下:

UUIDuuid3=UUID.nameUUIDFromBytes("test".getBytes());
intversion3=uuid3.version();
System.out.println("UUID:"+uuid3+"版本"+version3);

程式輸出結果如下所示:

UUID:098f6bcd-4621-3373-8ade-4e832627b4f6版本3

其二是 fromString()

publicstaticUUIDfromString(Stringname){
intlen=name.length();
if(len>36){
thrownewIllegalArgumentException("UUIDstringtoolarge");
}

intdash1=name.indexOf('-',0);
intdash2=name.indexOf('-',dash1+1);
intdash3=name.indexOf('-',dash2+1);
intdash4=name.indexOf('-',dash3+1);
intdash5=name.indexOf('-',dash4+1);

//Foranyvalidinput,dash1throughdash4willbepositiveanddash5
//negative,butit'senoughtocheckdash4anddash5:
//-ifdash1is-1,dash4willbe-1
//-ifdash1ispositivebutdash2is-1,dash4willbe-1
//-ifdash1anddash2ispositive,dash3willbe-1,dash4willbe
//positive,butsowilldash5
if(dash4<0||dash5>=0){
thrownewIllegalArgumentException("InvalidUUIDstring:"+name);
}

longmostSigBits=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;
longleastSigBits=Long.parseLong(name,dash3+1,dash4,16)&0xffffL;
leastSigBits<<=48;
leastSigBits|=Long.parseLong(name,dash4+1,len,16)&0xffffffffffffL;

returnnewUUID(mostSigBits,leastSigBits);
}

fromString() 方法會生成一個基於指定 UUID 字串的 UUID 物件,如果指定的 UUID 字串不符合 UUID 的格式,將丟擲 IllegalArgumentException 異常。

示例如下:

UUIDuuid=UUID.fromString("38400000-8cf0-11bd-b23e-10b96e4ef00d");
intversion=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沉默王二,一枚有趣的程式設計師
*/

publicclassUUIDVersionExample{
publicstaticvoidmain(String[]args){
UUIDuuid1=Generators.timeBasedGenerator().generate();
System.out.println("UUID:"+uuid1);
System.out.println("UUID版本:"+uuid1.version());

UUIDuuid2=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 的一切,我都已經說明白了。趕緊把這篇文章先發給小王預覽一下,讓他漲漲見識。


我是沉默王二,一枚有顏值卻靠才華苟且的程式設計師。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,奧利給

注:如果文章有任何問題,歡迎毫不留情地指正。

如果你覺得文章對你有些幫助歡迎微信搜尋「沉默王二」第一時間閱讀,回覆「小白」更有我肝了 4 萬+字的 Java 小白手冊 2.0 版,本文 GitHub github.com/itwanger 已收錄,歡迎 star。