這篇文章主要介紹“JDK中UUID的底層實(shí)現(xiàn)方式”,在日常操作中,相信很多人在JDK中UUID的底層實(shí)現(xiàn)方式問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”JDK中UUID的底層實(shí)現(xiàn)方式”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!
讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對(duì)這個(gè)行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡(jiǎn)單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名注冊(cè)、網(wǎng)頁空間、營(yíng)銷軟件、網(wǎng)站建設(shè)、東興網(wǎng)站維護(hù)、網(wǎng)站推廣。
前提
UUID是Universally Unique IDentifier的縮寫,翻譯為通用唯一標(biāo)識(shí)符或者全局唯一標(biāo)識(shí)符。對(duì)于UUID的描述,下面摘錄一下規(guī)范文件A Universally Unique IDentifier (UUID) URN Namespace中的一些描述:
UUID(也稱為GUID)定義了統(tǒng)一資源名稱命名空間。UUID的長(zhǎng)度為128比特,可以保證在空間和時(shí)間上的唯一性。
「動(dòng)機(jī):」
使用UUID的主要原因之一是不需要集中式管理,其中一種格式限定了IEEE 802節(jié)點(diǎn)標(biāo)識(shí)符,其他格式無此限制??梢宰詣?dòng)化按需生成UUID,應(yīng)用于多重不同的場(chǎng)景。UUID算法支持極高的分配速率,每臺(tái)機(jī)器每秒鐘可以生成超過1000萬個(gè)UUID,因此它們可以作為事務(wù)ID使用。UUID具有固定大小128比特,與其他替代方案相比,它具有體積小的優(yōu)勢(shì),非常適用于各種排序、散列和存儲(chǔ)在數(shù)據(jù)庫(kù)中,具有編程易用性的特點(diǎn)。
這里只需要記住UUID幾個(gè)核心特定:
全局時(shí)空唯一性
固定長(zhǎng)度128比特,也就是16字節(jié)(1 byte = 8 bit)
分配速率極高,單機(jī)每秒可以生成超過1000萬個(gè)UUID(實(shí)際上更高)
下面就JDK中的UUID實(shí)現(xiàn)詳細(xì)分析一下UUID生成算法。編寫本文的時(shí)候選用的JDK為JDK11。
再聊UUID
前面為了編寫簡(jiǎn)單的摘要,所以只粗略摘錄了規(guī)范文件里面的一些章節(jié),這里再詳細(xì)聊聊UUID的一些定義、碰撞概率等等。
UUID定義
UUID是一種軟件構(gòu)建的標(biāo)準(zhǔn),也是開放軟件基金會(huì)組織在分布式計(jì)算環(huán)境領(lǐng)域的一部分。提出此標(biāo)準(zhǔn)的目的是:讓分布式系統(tǒng)中的所有元素或者組件都有唯一的可辨別的信息,因?yàn)闃O低沖突頻率和高效算法的基礎(chǔ),它不需要集中式控制和管理唯一可辨別信息的生成,由此,每個(gè)使用者都可以自由地創(chuàng)建與其他人不沖突的UUID。
「UUID本質(zhì)是一個(gè)128比特的數(shù)字」,這是一個(gè)位長(zhǎng)巨大的數(shù)值,理論上來說,UUID的總數(shù)量為2^128個(gè)。這個(gè)數(shù)字大概可以這樣估算:如果「每納秒」產(chǎn)生「1兆」個(gè)不相同的UUID,需要花費(fèi)超過100億年才會(huì)用完所有的UUID。
UUID的變體與版本
UUID標(biāo)準(zhǔn)和算法定義的時(shí)候,為了考慮歷史兼容性和未來的擴(kuò)展,提供了多種變體和版本。接下來的變體和版本描述來源于維基百科中的Versions章節(jié)和RFC 4122中的Variant章節(jié)。
目前已知的變體如下:
變體0xx:Reserved, NCS backward compatibility,為向后兼容做預(yù)留的變體
變體10x:The IETF aka Leach-Salz variant (used by this class),稱為L(zhǎng)each–Salz UUID或者IETF UUID,JDK中UUID目前正在使用的變體
變體110:Reserved, Microsoft Corporation backward compatibility,微軟早期GUID預(yù)留變體
變體111:Reserved for future definition,將來擴(kuò)展預(yù)留,目前還沒被使用的變體
目前已知的版本如下:
空UUID(特殊版本0),用00000000-0000-0000-0000-000000000000表示,也就是所有的比特都是0
date-time and MAC address(版本1):基于時(shí)間和MAC地址的版本,通過計(jì)算當(dāng)前時(shí)間戳、隨機(jī)數(shù)和機(jī)器MAC地址得到。由于有MAC地址,這個(gè)可以保證其在全球的唯一性。但是使用了MAC地址,就會(huì)有MAC地址暴露問題。若是局域網(wǎng),可以用IP地址代替
date-time and MAC address, DCE security version(版本2):分布式計(jì)算環(huán)境安全的UUID,算法和版本1基本一致,但會(huì)把時(shí)間戳的前4位置換為POSIX的UID或GID
namespace name-based MD5(版本3):通過計(jì)算名字和命名空間的MD5散列值得到。這個(gè)版本的UUID保證了:相同命名空間中不同名字生成的UUID的唯一性;不同命名空間中的UUID的唯一性;相同命名空間中相同名字的UUID重復(fù)生成是相同的
random(版本4):根據(jù)隨機(jī)數(shù),或者偽隨機(jī)數(shù)生成UUID。這種UUID產(chǎn)生重復(fù)的概率是可以計(jì)算出來的,還有一個(gè)特點(diǎn)就是預(yù)留了6比特存放變體和版本屬性,所以隨機(jī)生成的位一共有122個(gè),總量為2^122,比其他變體的總量要偏少
namespace name-based SHA-1(版本5):和版本3類似,散列算法換成了SHA-1
其中,JDK中應(yīng)用的變體是Leach-Salz,提供了namespace name-based MD5(版本3)和random(版本4)兩個(gè)版本的UUID生成實(shí)現(xiàn)。
UUID的格式
在規(guī)范文件描述中,UUID是由16個(gè)8比特?cái)?shù)字,或者說32個(gè)16進(jìn)制表示形式下的字符組成,一般表示形式為8-4-4-4-12,加上連接字符-一共有36個(gè)字符,例如:
## 例子 123e4567-e89b-12d3-a456-426614174000 ## 通用格式 xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
其中4比特長(zhǎng)度的M和1到3比特長(zhǎng)度的N分別代表版本號(hào)和變體標(biāo)識(shí)。UUID的具體布局如下:
屬性 | 屬性名 | 長(zhǎng)度(bytes ) | 長(zhǎng)度(16 進(jìn)制字符) | 內(nèi)容 |
---|---|---|---|---|
time_low | 時(shí)間戳低位 | 4 | 8 | 代表時(shí)間戳的低32 比特的整數(shù)表示 |
time_mid | 時(shí)間戳中位 | 2 | 4 | 代表時(shí)間戳的中間16 比特的整數(shù)表示 |
time_hi_and_version | 時(shí)間戳高位和版本號(hào) | 2 | 4 | 高位4 比特是版本號(hào)表示,剩余是時(shí)間戳的高12 比特的整數(shù)表示 |
clock_seq_hi_and_res clock_seq_low | 時(shí)鐘序列與變體編號(hào) | 2 | 4 | 最高位1 到3 比特表示變體編號(hào),剩下的13 到15 比特表示時(shí)鐘序列 |
node | 節(jié)點(diǎn)ID | 6 | 12 | 48 比特表示的節(jié)點(diǎn)ID |
基于這個(gè)表格畫一個(gè)圖:
「嚴(yán)重注意,重復(fù)三次」:
上面提到的UUID的具體布局只適用于date-time and MAC address(版本1)和date-time and MAC address, DCE security version(版本2),其他版本雖然采用了基本一樣的字段分布,但是無法獲取時(shí)間戳、時(shí)鐘序列或者節(jié)點(diǎn)ID等信息
上面提到的UUID的具體布局只適用于date-time and MAC address(版本1)和date-time and MAC address, DCE security version(版本2),其他版本雖然采用了基本一樣的字段分布,但是無法獲取時(shí)間戳、時(shí)鐘序列或者節(jié)點(diǎn)ID等信息
上面提到的UUID的具體布局只適用于date-time and MAC address(版本1)和date-time and MAC address, DCE security version(版本2),其他版本雖然采用了基本一樣的字段分布,但是無法獲取時(shí)間戳、時(shí)鐘序列或者節(jié)點(diǎn)ID等信息
JDK中只提供了版本3和版本4的實(shí)現(xiàn),但是java.util.UUID的布局采用了上面表格的字段
UUID的碰撞幾率計(jì)算
UUID的總量雖然巨大,但是如果不停地使用,假設(shè)每納秒生成超過1兆個(gè)UUID并且人類有幸能夠繁衍到100億年以后,總會(huì)有可能產(chǎn)生重復(fù)的UUID。那么,怎么計(jì)算UUID的碰撞幾率呢?這是一個(gè)數(shù)學(xué)問題,可以使用比較著名的「生日悖論」解決:
上圖來源于某搜索引擎百科。剛好維基百科上給出了碰撞幾率的計(jì)算過程,其實(shí)用的也是生日悖論的計(jì)算方法,這里貼一下:
上面的碰撞幾率計(jì)算是基于Leach–Salz變體和版本4進(jìn)行,得到的結(jié)論是:
103萬億個(gè)UUID中找到重復(fù)項(xiàng)的概率是十億分之一
要生成一個(gè)沖突率達(dá)到50%的UUID至少需要生成2.71 * 1_000_000^3個(gè)UUID
有生之年不需要擔(dān)心UUID沖突,出現(xiàn)的可能性比大型隕石撞地球還低。
UUID的使用場(chǎng)景
基本所有需要使用全局唯一標(biāo)識(shí)符的場(chǎng)景都可以使用UUID,除非對(duì)長(zhǎng)度有明確的限制,常用的場(chǎng)景包括:
日志框架映射診斷上下文中的TRACE_ID
APM工具或者說OpenTracing規(guī)范中的SPAN_ID
特殊場(chǎng)景下數(shù)據(jù)庫(kù)主鍵或者虛擬外鍵
交易ID(訂單ID)
等等......
JDK中UUID詳細(xì)介紹和使用
這里先介紹使用方式。前面提到JDK中應(yīng)用的變體是Leach-Salz(變體2),提供了namespace name-based MD5(版本3)和random(版本4)兩個(gè)版本的UUID生成實(shí)現(xiàn),實(shí)際上java.util.UUID提供了四種生成UUID實(shí)例的方式:
最常見的就是調(diào)用靜態(tài)方法UUID#randomUUID(),這就是版本4的靜態(tài)工廠方法
其次是調(diào)用靜態(tài)方法UUID#nameUUIDFromBytes(byte[] name),這就是版本3的靜態(tài)工廠方法
另外有調(diào)用靜態(tài)方法UUID#fromString(String name),這是解析8-4-4-4-12格式字符串生成UUID實(shí)例的靜態(tài)工廠方法
還有低層次的構(gòu)造函數(shù)UUID(long mostSigBits, long leastSigBits),這個(gè)對(duì)于使用者來說并不常見
最常用的方法有實(shí)例方法toString(),把UUID轉(zhuǎn)化為16進(jìn)制字符串拼接而成的8-4-4-4-12形式表示,例如:
String uuid = UUID.randomUUID().toString();
其他Getter方法:
UUID uuid = UUID.randomUUID(); // 返回版本號(hào) int version = uuid.version(); // 返回變體號(hào) int variant = uuid.variant(); // 返回時(shí)間戳 - 這個(gè)方法會(huì)報(bào)錯(cuò),只有Time-based UUID也就是版本1或者2的UUID實(shí)現(xiàn)才能返回時(shí)間戳 long timestamp = uuid.timestamp(); // 返回時(shí)鐘序列 - 這個(gè)方法會(huì)報(bào)錯(cuò),只有Time-based UUID也就是版本1或者2的UUID實(shí)現(xiàn)才能返回時(shí)鐘序列 long clockSequence = uuid.clockSequence(); // 返回節(jié)點(diǎn)ID - 這個(gè)方法會(huì)報(bào)錯(cuò),只有Time-based UUID也就是版本1或者2的UUID實(shí)現(xiàn)才能返回節(jié)點(diǎn)ID long nodeId = uuid.node();
可以驗(yàn)證一下不同靜態(tài)工廠方法的版本和變體號(hào):
UUID uuid = UUID.randomUUID(); int version = uuid.version(); int variant = uuid.variant(); System.out.println(String.format("version:%d,variant:%d", version, variant)); uuid = UUID.nameUUIDFromBytes(new byte[0]); version = uuid.version(); variant = uuid.variant(); System.out.println(String.format("version:%d,variant:%d", version, variant)); // 輸出結(jié)果 version:4,variant:2 version:3,variant:2
探究JDK中UUID源碼實(shí)現(xiàn)
java.util.UUID被final修飾,實(shí)現(xiàn)了Serializable和Comparable接口,從一般理解上看,有下面的特定:
不可變,一般來說工具類都是這樣定義的
可序列化和反序列化
不同的對(duì)象之間可以進(jìn)行比較,比較方法后面會(huì)分析
下面會(huì)從不同的方面分析一下java.util.UUID的源碼實(shí)現(xiàn):
屬性和構(gòu)造函數(shù)
隨機(jī)數(shù)版本實(shí)現(xiàn)
namespace name-based MD5版本實(shí)現(xiàn)
其他實(shí)現(xiàn)
格式化輸出
比較相關(guān)的方法
屬性和構(gòu)造函數(shù)
前面反復(fù)提到JDK中只提供了版本3和版本4的實(shí)現(xiàn),但是java.util.UUID的布局采用了UUID規(guī)范中的字段定義,長(zhǎng)度一共128比特,剛好可以存放在兩個(gè)long類型的整數(shù)中,所以看到了UUID類中存在兩個(gè)long類型的整型數(shù)值:
public final class UUID implements java.io.Serializable, Comparable<UUID> { // 暫時(shí)省略其他代碼 /* * The most significant 64 bits of this UUID. * UUID中有效的高64比特 * * @serial */ private final long mostSigBits; /* * The least significant 64 bits of this UUID. * UUID中有效的低64比特 * * @serial */ private final long leastSigBits; // 暫時(shí)省略其他代碼 }
從UUID類注釋中可以看到具體的字段布局如下:
「高64比特mostSigBits的布局」
字段 | bit 長(zhǎng)度 | 16 進(jìn)制字符長(zhǎng)度 |
---|---|---|
time_low | 32 | 8 |
time_mid | 16 | 4 |
version | 4 | 1 |
time_hi | 12 | 3 |
「低64比特leastSigBits的布局」
字段 | bit 長(zhǎng)度 | 16 進(jìn)制字符長(zhǎng)度 |
---|---|---|
variant | 2 | 小于1 |
clock_seq | 14 | variant 和clock_seq 加起來等于4 |
node | 48 | 12 |
接著看UUID的其他成員屬性和構(gòu)造函數(shù):
public final class UUID implements java.io.Serializable, Comparable<UUID> { // 暫時(shí)省略其他代碼 // Java語言訪問類,里面存放了很多底層相關(guān)的訪問或者轉(zhuǎn)換方法,在UUID中主要是toString()實(shí)例方法用來格式化成8-4-4-4-12的形式,委托到Long.fastUUID()方法 private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); // 靜態(tài)內(nèi)部類確保SecureRandom初始化,用于版本4的隨機(jī)數(shù)UUID版本生成安全隨機(jī)數(shù) private static class Holder { static final SecureRandom numberGenerator = new SecureRandom(); } // 通過長(zhǎng)度為16的字節(jié)數(shù)組,計(jì)算mostSigBits和leastSigBits的值初始化UUID實(shí)例 private UUID(byte[] data) { long msb = 0; long lsb = 0; assert data.length == 16 : "data must be 16 bytes in length"; for (int i=0; i<8; i++) msb = (msb << 8) | (data[i] & 0xff); for (int i=8; i<16; i++) lsb = (lsb << 8) | (data[i] & 0xff); this.mostSigBits = msb; this.leastSigBits = lsb; } // 直接指定mostSigBits和leastSigBits構(gòu)造UUID實(shí)例 public UUID(long mostSigBits, long leastSigBits) { this.mostSigBits = mostSigBits; this.leastSigBits = leastSigBits; } // 暫時(shí)省略其他代碼 }
私有構(gòu)造private UUID(byte[] data)中有一些位運(yùn)算技巧:
long msb = 0; long lsb = 0; assert data.length == 16 : "data must be 16 bytes in length"; for (int i=0; i<8; i++) msb = (msb << 8) | (data[i] & 0xff); for (int i=8; i<16; i++) lsb = (lsb << 8) | (data[i] & 0xff); this.mostSigBits = msb; this.leastSigBits = lsb;
輸入的字節(jié)數(shù)組長(zhǎng)度為16,mostSigBits由字節(jié)數(shù)組的前8個(gè)字節(jié)轉(zhuǎn)換而來,而leastSigBits由字節(jié)數(shù)組的后8個(gè)字節(jié)轉(zhuǎn)換而來。中間變量msb或者lsb在提取字節(jié)位進(jìn)行計(jì)算的時(shí)候:
先進(jìn)行左移8位確保需要計(jì)算的位為0,已經(jīng)計(jì)算好的位移動(dòng)到左邊
然后右邊需要提取的字節(jié)data[i]的8位會(huì)先和0xff(補(bǔ)碼1111 1111)進(jìn)行或運(yùn)算,確保不足8位的高位被補(bǔ)充為0,超過8位的高位會(huì)被截?cái)酁榈?位,也就是data[i] & 0xff確保得到的補(bǔ)碼為8位
前面兩步的結(jié)果再進(jìn)行或運(yùn)算
一個(gè)模擬過程如下:
(為了區(qū)分明顯,筆者每4位加了一個(gè)下劃線) (為了簡(jiǎn)答,只看字節(jié)數(shù)組的前4個(gè)字節(jié),同時(shí)只看long類型的前4個(gè)字節(jié)) 0xff === 1111_1111 long msb = 0 => 0000_0000 0000_0000 0000_0000 0000_0000 byte[] data 0000_0001 0000_0010 0000_0100 0000_1000 i = 0(第一輪) msb << 8 = 0000_0000 0000_0000 0000_0000 0000_0000 data[i] & 0xff = 0000_0001 & 1111_1111 = 0000_0001 (msb << 8) | (data[i] & 0xff) = 0000_0000 0000_0000 0000_0000 0000_0001 (第一輪 msb = 0000_0000 0000_0000 0000_0000 0000_0001) i = 1(第二輪) msb << 8 = 0000_0000 0000_0000 0000_0001 0000_0000 data[i] & 0xff = 0000_0010 & 1111_1111 = 0000_0010 (msb << 8) | (data[i] & 0xff) = 0000_0000 0000_0000 0000_0001 0000_0010 (第二輪 msb = 0000_0000 0000_0000 0000_0001 0000_0010) i = 2(第三輪) msb << 8 = 0000_0000 0000_0001 0000_0010 0000_0000 data[i] & 0xff = 0000_0100 & 1111_1111 = 0000_0100 (msb << 8) | (data[i] & 0xff) = 0000_0000 0000_0001 0000_0010 0000_0100 (第三輪 msb = 0000_0000 0000_0001 0000_0010 0000_0100) i = 3(第四輪) msb << 8 = 0000_0001 0000_0010 0000_0100 0000000 data[i] & 0xff = 0000_1000 & 1111_1111 = 0000_1000 (msb << 8) | (data[i] & 0xff) = 0000_0001 0000_0010 0000_0100 0000_1000 (第四輪 msb = 0000_0001 0000_0010 0000_0100 0000_1000)
以此類推,這個(gè)私有構(gòu)造函數(shù)執(zhí)行完畢后,長(zhǎng)度為16的字節(jié)數(shù)組的所有位就會(huì)轉(zhuǎn)移到mostSigBits和leastSigBits中。
隨機(jī)數(shù)版本實(shí)現(xiàn)
構(gòu)造函數(shù)分析完,接著分析重磅的靜態(tài)工廠方法UUID#randomUUID(),這是使用頻率最高的一個(gè)方法:
public static UUID randomUUID() { // 靜態(tài)內(nèi)部類Holder持有的SecureRandom實(shí)例,確保提前初始化 SecureRandom ng = Holder.numberGenerator; // 生成一個(gè)16字節(jié)的安全隨機(jī)數(shù),放在長(zhǎng)度為16的字節(jié)數(shù)組中 byte[] randomBytes = new byte[16]; ng.nextBytes(randomBytes); // 清空版本號(hào)所在的位,重新設(shè)置為4 randomBytes[6] &= 0x0f; /* clear version */ randomBytes[6] |= 0x40; /* set to version 4 */ // 清空變體號(hào)所在的位,重新設(shè)置為 randomBytes[8] &= 0x3f; /* clear variant */ randomBytes[8] |= 0x80; /* set to IETF variant */ return new UUID(randomBytes); }
關(guān)于上面的位運(yùn)算,這里可以使用極端的例子進(jìn)行推演:
假設(shè)randomBytes[6] = 1111_1111 // 清空version位 randomBytes[6] &= 0x0f => 1111_1111 & 0000_1111 = 0000_1111 得到randomBytes[6] = 0000_1111 (這里可見高4比特被清空為0) // 設(shè)置version位為整數(shù)4 => 十六進(jìn)制0x40 => 二級(jí)制補(bǔ)碼0100_0000 randomBytes[6] |= 0x40 => 0000_1111 | 0100_0000 = 0100_1111 得到randomBytes[6] = 0100_1111 結(jié)果:version位 => 0100(4 bit)=> 對(duì)應(yīng)十進(jìn)制數(shù)4 同理 假設(shè)randomBytes[8] = 1111_1111 // 清空variant位 randomBytes[8] &= 0x3f => 1111_1111 & 0011_1111 = 0011_1111 // 設(shè)置variant位為整數(shù)128 => 十六進(jìn)制0x80 => 二級(jí)制補(bǔ)碼1000_0000 (這里取左邊高位2位) randomBytes[8] |= 0x80 => 0011_1111 | 1000_0000 = 1011_1111 結(jié)果:variant位 => 10(2 bit)=> 對(duì)應(yīng)十進(jìn)制數(shù)2
關(guān)于UUID里面的Getter方法例如version()、variant()其實(shí)就是找到對(duì)應(yīng)的位,并且轉(zhuǎn)換為十進(jìn)制整數(shù)返回,如果熟練使用位運(yùn)算,應(yīng)該不難理解,后面不會(huì)分析這類的Getter方法。
「隨機(jī)數(shù)版本實(shí)現(xiàn)強(qiáng)依賴于SecureRandom生成的隨機(jī)數(shù)(字節(jié)數(shù)組)」。SecureRandom的引擎提供者可以從sun.security.provider.SunEntries中查看,對(duì)于不同系統(tǒng)版本的JDK實(shí)現(xiàn)會(huì)選用不同的引擎,常見的如NativePRNG。JDK11配置文件$JAVA_HOME/conf/security/java.security中的securerandom.source屬性用于指定系統(tǒng)默認(rèn)的隨機(jī)源:
這里要提一個(gè)小知識(shí)點(diǎn),想要得到密碼學(xué)意義上的安全隨機(jī)數(shù),可以直接使用真隨機(jī)數(shù)產(chǎn)生器產(chǎn)生的隨機(jī)數(shù),或者使用真隨機(jī)數(shù)產(chǎn)生器產(chǎn)生的隨機(jī)數(shù)做種子。通過查找一些資料得知「非物理真隨機(jī)數(shù)產(chǎn)生器」有:
Linux操作系統(tǒng)的/dev/random設(shè)備接口
Windows操作系統(tǒng)的CryptGenRandom接口
如果不修改java.security配置文件,默認(rèn)隨機(jī)數(shù)提供引擎會(huì)根據(jù)不同的操作系統(tǒng)選用不同的實(shí)現(xiàn),這里不進(jìn)行深究。在Linux環(huán)境下,SecureRandom實(shí)例化后,不通過setSeed()方法設(shè)置隨機(jī)數(shù)作為種子,默認(rèn)就是使用/dev/random提供的安全隨機(jī)數(shù)接口獲取種子,產(chǎn)生的隨機(jī)數(shù)是密碼學(xué)意義上的安全隨機(jī)數(shù)?!敢痪湓捀爬?,UUID中的私有靜態(tài)內(nèi)部類Holder中的SecureRandom實(shí)例可以產(chǎn)生安全隨機(jī)數(shù),這個(gè)是JDK實(shí)現(xiàn)UUID版本4的一個(gè)重要前提」。這里總結(jié)一下隨機(jī)數(shù)版本UUID的實(shí)現(xiàn)步驟:
通過SecureRandom依賴提供的安全隨機(jī)數(shù)接口獲取種子,生成一個(gè)16字節(jié)的隨機(jī)數(shù)(字節(jié)數(shù)組)
對(duì)于生成的隨機(jī)數(shù),清空和重新設(shè)置version和variant對(duì)應(yīng)的位
把重置完version和variant的隨機(jī)數(shù)的所有位轉(zhuǎn)移到mostSigBits和leastSigBits中
namespace name-based MD5版本實(shí)現(xiàn)
接著分析版本3也就是namespace name-based MD5版本的實(shí)現(xiàn),對(duì)應(yīng)于靜態(tài)工廠方法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); }
它的后續(xù)基本處理和隨機(jī)數(shù)版本基本一致(清空版本位的時(shí)候,重新設(shè)置為3),唯一明顯不同的地方就是生成原始隨機(jī)數(shù)的時(shí)候,采用的方式是:基于輸入的name字節(jié)數(shù)組,通過MD5摘要算法生成一個(gè)MD5摘要字節(jié)數(shù)組作為原始安全隨機(jī)數(shù),返回的這個(gè)隨機(jī)數(shù)剛好也是16字節(jié)長(zhǎng)度的。使用方式很簡(jiǎn)單:
UUID uuid = UUID.nameUUIDFromBytes("throwable".getBytes());
namespace name-based MD5版本UUID的實(shí)現(xiàn)步驟如下:
通過輸入的命名字節(jié)數(shù)組基于MD5算法生成一個(gè)16字節(jié)長(zhǎng)度的隨機(jī)數(shù)
對(duì)于生成的隨機(jī)數(shù),清空和重新設(shè)置version和variant對(duì)應(yīng)的位
把重置完version和variant的隨機(jī)數(shù)的所有位轉(zhuǎn)移到mostSigBits和leastSigBits中
namespace name-based MD5版本的UUID強(qiáng)依賴于MD5算法,有個(gè)明顯的特征是如果輸入的byte[] name一致的情況下,會(huì)產(chǎn)生完全相同的UUID實(shí)例。
其他實(shí)現(xiàn)
其他實(shí)現(xiàn)主要包括:
// 完全定制mostSigBits和leastSigBits,可以參考UUID標(biāo)準(zhǔn)字段布局進(jìn)行設(shè)置,也可以按照自行制定的標(biāo)準(zhǔn) public UUID(long mostSigBits, long leastSigBits) { this.mostSigBits = mostSigBits; this.leastSigBits = leastSigBits; } // 基于字符串格式8-4-4-4-12的UUID輸入,重新解析出mostSigBits和leastSigBits,這個(gè)靜態(tài)工廠方法也不常用,里面的位運(yùn)算也不進(jìn)行詳細(xì)探究 public static UUID fromString(String name) { int len = name.length(); if (len > 36) { throw new IllegalArgumentException("UUID string too large"); } int dash2 = name.indexOf('-', 0); int dash3 = name.indexOf('-', dash2 + 1); int dash4 = name.indexOf('-', dash3 + 1); int dash5 = name.indexOf('-', dash4 + 1); int dash6 = name.indexOf('-', dash5 + 1); if (dash5 < 0 || dash6 >= 0) { throw new IllegalArgumentException("Invalid UUID string: " + name); } long mostSigBits = Long.parseLong(name, 0, dash2, 16) & 0xffffffffL; mostSigBits <<= 16; mostSigBits |= Long.parseLong(name, dash2 + 1, dash3, 16) & 0xffffL; mostSigBits <<= 16; mostSigBits |= Long.parseLong(name, dash3 + 1, dash4, 16) & 0xffffL; long leastSigBits = Long.parseLong(name, dash4 + 1, dash5, 16) & 0xffffL; leastSigBits <<= 48; leastSigBits |= Long.parseLong(name, dash5 + 1, len, 16) & 0xffffffffffffL; return new UUID(mostSigBits, leastSigBits); }
格式化輸出
格式化輸出體現(xiàn)在UUID#toString()方法,這個(gè)方法會(huì)把mostSigBits和leastSigBits格式化為8-4-4-4-12的形式,這里詳細(xì)分析一下格式化的過程。首先從注釋上看格式是:
<time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node> time_low = 4 * <hexOctet> => 4個(gè)16進(jìn)制8位字符 time_mid = 2 * <hexOctet> => 2個(gè)16進(jìn)制8位字符 time_high_and_version = 4 * <hexOctet> => 2個(gè)16進(jìn)制8位字符 variant_and_sequence = 4 * <hexOctet> => 2個(gè)16進(jìn)制8位字符 node = 4 * <hexOctet> => 6個(gè)16進(jìn)制8位字符 hexOctet = <hexDigit><hexDigit>(2個(gè)hexDigit) hexDigit = 0-9a-F(其實(shí)就是16進(jìn)制的字符)
和前文布局分析時(shí)候的提到的內(nèi)容一致。UUID#toString()方法源碼如下:
private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); public String toString() { return jla.fastUUID(leastSigBits, mostSigBits); } ↓↓↓↓↓↓↓↓↓↓↓↓ // java.lang.System private static void setJavaLangAccess() { SharedSecrets.setJavaLangAccess(new JavaLangAccess() { public String fastUUID(long lsb, long msb) { return Long.fastUUID(lsb, msb); } } ↓↓↓↓↓↓↓↓↓↓↓↓ // java.lang.Long static String fastUUID(long lsb, long msb) { // COMPACT_STRINGS在String類中默認(rèn)為true,所以會(huì)命中if分支 if (COMPACT_STRINGS) { // 初始化36長(zhǎng)度的字節(jié)數(shù)組 byte[] buf = new byte[36]; // lsb的低48位轉(zhuǎn)換為16進(jìn)制格式寫入到buf中 - node => 位置[24,35] formatUnsignedLong0(lsb, 4, buf, 24, 12); // lsb的高16位轉(zhuǎn)換為16進(jìn)制格式寫入到buf中 - variant_and_sequence => 位置[19,22] formatUnsignedLong0(lsb >>> 48, 4, buf, 19, 4); // msb的低16位轉(zhuǎn)換為16進(jìn)制格式寫入到buf中 - time_high_and_version => 位置[14,17] formatUnsignedLong0(msb, 4, buf, 14, 4); // msb的中16位轉(zhuǎn)換為16進(jìn)制格式寫入到buf中 - time_mid => 位置[9,12] formatUnsignedLong0(msb >>> 16, 4, buf, 9, 4); // msb的高32位轉(zhuǎn)換為16進(jìn)制格式寫入到buf中 - time_low => 位置[0,7] formatUnsignedLong0(msb >>> 32, 4, buf, 0, 8); // 空余的字節(jié)槽位插入'-',剛好占用了4個(gè)字節(jié) buf[23] = '-'; buf[18] = '-'; buf[13] = '-'; buf[8] = '-'; // 基于處理好的字節(jié)數(shù)組,實(shí)例化String,并且編碼指定為L(zhǎng)ATIN1 return new String(buf, LATIN1); } else { byte[] buf = new byte[72]; formatUnsignedLong0UTF16(lsb, 4, buf, 24, 12); formatUnsignedLong0UTF16(lsb >>> 48, 4, buf, 19, 4); formatUnsignedLong0UTF16(msb, 4, buf, 14, 4); formatUnsignedLong0UTF16(msb >>> 16, 4, buf, 9, 4); formatUnsignedLong0UTF16(msb >>> 32, 4, buf, 0, 8); StringUTF16.putChar(buf, 23, '-'); StringUTF16.putChar(buf, 18, '-'); StringUTF16.putChar(buf, 13, '-'); StringUTF16.putChar(buf, 8, '-'); return new String(buf, UTF16); } } /** * 格式化無符號(hào)的長(zhǎng)整型,填充到字節(jié)緩沖區(qū)buf中,如果長(zhǎng)度len超過了輸入值的ASCII格式表示,則會(huì)使用0進(jìn)行填充 * 這個(gè)方法就是把輸入長(zhǎng)整型值val,對(duì)應(yīng)一段長(zhǎng)度的位,填充到字節(jié)數(shù)組buf中,len控制寫入字符的長(zhǎng)度,offset控制寫入buf的起始位置 * 而shift參數(shù)決定基礎(chǔ)格式,4是16進(jìn)制,1是2進(jìn)制,3是8位 */ static void formatUnsignedLong0(long val, int shift, byte[] buf, int offset, int len) { int charPos = offset + len; int radix = 1 << shift; int mask = radix - 1; do { buf[--charPos] = (byte)Integer.digits[((int) val) & mask]; val >>>= shift; } while (charPos > offset); }
比較相關(guān)的方法
比較相關(guān)方法如下:
// hashCode方法基于mostSigBits和leastSigBits做異或得出一個(gè)中間變量hilo,再以32為因子進(jìn)行計(jì)算 public int hashCode() { long hilo = mostSigBits ^ leastSigBits; return ((int)(hilo >> 32)) ^ (int) hilo; } // equals為實(shí)例對(duì)比方法,直接對(duì)比兩個(gè)UUID的mostSigBits和leastSigBits值,完全相等的時(shí)候返回true public boolean equals(Object obj) { if ((null == obj) || (obj.getClass() != UUID.class)) return false; UUID id = (UUID)obj; return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); } // 比較規(guī)則是mostSigBits高位大者為大,高位相等的情況下,leastSigBits大者為大 public int compareTo(UUID val) { // The ordering is intentionally set up so that the UUIDs // can simply be numerically compared as two numbers return (this.mostSigBits < val.mostSigBits ? -1 : (this.mostSigBits > val.mostSigBits ? 1 : (this.leastSigBits < val.leastSigBits ? -1 : (this.leastSigBits > val.leastSigBits ? 1 : 0)))); }
所有比較方法僅僅和mostSigBits和leastSigBits有關(guān),畢竟這兩個(gè)長(zhǎng)整型就存儲(chǔ)了UUID實(shí)例的所有信息。
到此,關(guān)于“JDK中UUID的底層實(shí)現(xiàn)方式”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!
網(wǎng)站名稱:JDK中UUID的底層實(shí)現(xiàn)方式
網(wǎng)頁地址:http://www.muchs.cn/article46/jpjjeg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供手機(jī)網(wǎng)站建設(shè)、網(wǎng)站內(nèi)鏈、定制網(wǎng)站、網(wǎng)站改版、微信小程序、網(wǎng)站導(dǎo)航
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)