深度解讀UUID:結(jié)構(gòu)、原理以及生成機(jī)制
UUID 是什么
UUID (Universally Unique IDentifier) 通用唯一識(shí)別碼 ,也稱為 GUID (Globally Unique IDentifier) 全球唯一標(biāo)識(shí)符。
UUID是一個(gè)長(zhǎng)度為128位的標(biāo)志符,能夠在時(shí)間和空間上確保其唯一性。UUID最初應(yīng)用于Apollo網(wǎng)絡(luò)計(jì)算系統(tǒng),隨后在Open Software Foundation(OSF)的分布式計(jì)算環(huán)境(DCE)中得到應(yīng)用??勺尫植际较到y(tǒng)可以不借助中心節(jié)點(diǎn),就可以生成唯一標(biāo)識(shí), 比如唯一的ID進(jìn)行日志記錄。
并被微軟Windows平臺(tái)采用。Windows 舉例2個(gè)使用場(chǎng)景:
- COM組件通過GUID 來定義類標(biāo)識(shí)符(CLSID)、接口標(biāo)識(shí)符(IID)以及其他重要的標(biāo)識(shí),確保在整個(gè)系統(tǒng)中不會(huì)發(fā)生命名沖突。
圖片
截圖_20242927042948.png
- Windows注冊(cè)表中很多項(xiàng)都使用GUID作為子鍵名,以便為特定程序或功能提供一個(gè)全球唯一的注冊(cè)表路徑。
圖片
UUID之所以被廣泛采用,主要原因之一是它們的分配不需要中心管理機(jī)構(gòu)介入。其具有唯一性和持久性,它們非常適合用作統(tǒng)一資源名稱(URN)。UUID能夠無需注冊(cè)過程就能生成新的標(biāo)識(shí)符的獨(dú)特優(yōu)點(diǎn),使得UUID成為創(chuàng)建成本最低的URN類型之一。
那么UUID會(huì)重復(fù)嘛,由于UUID具有固定的大小并包含時(shí)間字段,在特定算法下,隨著時(shí)間推移,理論上在大約公元3400年左右會(huì)出現(xiàn)值的循環(huán),所以問題不大。
由于UUID是一個(gè)128位的長(zhǎng)的標(biāo)志符,為了便于閱讀和顯示,通常會(huì)將這個(gè)大整數(shù)轉(zhuǎn)換成32(不包含連接符)個(gè)十六進(jìn)制字符組成的字符串形式。如下
crypto.randomUUID()
// 4d93f326-3f48-4a43-929d-b6489f4754b5
`${crypto.randomUUID()}`.length
// 長(zhǎng)度:36
`${crypto.randomUUID()}`.replace(/-/g, '').length
// 去掉連接符:32
這128位的組成,以及是怎么變成32位的十六進(jìn)制字符的,繼續(xù)往下看:
UUID 的結(jié)構(gòu)
UUID看似雜亂無章,其實(shí)內(nèi)有乾坤,慢慢道來。
必須了解的
- 比特(bit):二進(jìn)制數(shù)字系統(tǒng)中的基本單位。一個(gè)比特可以代表二進(jìn)制中的一個(gè)0或1。
- 位(通常情況下與比特同義):二進(jìn)制數(shù)系統(tǒng)中的一位,同樣表示0或1。
- 字節(jié)(Byte):字節(jié)是計(jì)算機(jī)中更常用的單位,用于衡量數(shù)據(jù)存儲(chǔ)容量和傳輸速率。1字節(jié)等于8個(gè)比特。
總結(jié)起來就是:
- 1 字節(jié) = 8 位
- 1 位 = 1 比特
128位轉(zhuǎn)為32個(gè)十六進(jìn)制字符, 這個(gè)十六進(jìn)制字符是什么呢,其專業(yè)名字為hexDigit,是UUID中我們?nèi)庋劭梢姷淖钚卧?/p>
hexDigit
hexDigit , 十六進(jìn)制數(shù)字字符,是一個(gè)長(zhǎng)度為4比特,可以表示0(0b000)到15(0b1111)之間數(shù)值。其能轉(zhuǎn)為16進(jìn)制的相對(duì)應(yīng)符號(hào),其取值范圍為 0-9,a-f, A-F, 即0123456789abcdefABCDEF某一個(gè)值。
所以, hexDigit 可以粗暴的理解為 0123456789abcdefABCDEF某一個(gè)值。
(0b1000).toString(16) // 8
(0b1111).toString(16) // F
此外,還有一個(gè)hexOctet, 兩個(gè)連續(xù)hexDigit組成的序列, 占8個(gè)比特,即一個(gè)字節(jié)。
UUID 基本結(jié)構(gòu)
在協(xié)議 RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace[1] 的 4.1.2. Layout and Byte Order[2] 有結(jié)構(gòu)圖:
圖片
這個(gè)圖有點(diǎn)小迷惑, 最上面的 0,1,2,3 不是表示位數(shù),就是簡(jiǎn)單的表示10位數(shù)的值,9之后就是 10, 11, 12等等。
這圖不太好理解,換一張手工畫的圖(UUID 10類型的V4版本):10類型和V4版本后續(xù)會(huì)解釋
圖片
128比特,16個(gè)字節(jié)即 16 hexOctet,就被如下瓜分了。
字段 | hexOctet(字節(jié)) | 位置 | 備注 |
time_low | 4 | 0-3 | 時(shí)間戳 的低位部分 |
time_mid | 2 | 4-5 | 時(shí)間戳的中間部分 |
time_hi_and_version | 2 | 6-7 | 時(shí)間戳高位部分與 版本 字段,其中12位代表時(shí)間戳的高12位,4位則用來標(biāo)識(shí)UUID的版本號(hào) |
clock_seq_hi_and_reserved | 1 | 8 | 時(shí)鐘序列 高位與 保留位 |
clock_seq_low | 1 | 9 | 時(shí)鐘序列低位 |
node | 6 | 10-15 | 節(jié)點(diǎn)標(biāo)識(shí)符,提供空間唯一性,通?;贛AC地址或隨機(jī)數(shù)生成,以確保全局范圍內(nèi)的唯一性 |
要想完整理解這個(gè) 6 部分組成,必然要理解備注中被加粗的幾個(gè)概念。保留位,版本, 時(shí)間戳, 時(shí)鐘序列 ,節(jié)點(diǎn)標(biāo)志符
類型(變體) 和保留位
UUID可以分為四種類型(變體),怎么識(shí)別是哪種類型(變體呢),UUID有對(duì)應(yīng)的Variant字段去標(biāo)記,可以參見協(xié)議的 4.1.1. Variant[3]部分。
variant字段位于UUID的第8個(gè)字節(jié) 即 clock_seq_hi_and_reserved 部分的第6-7位。
圖片
以外所有其他位的含義都是依據(jù)variant字段中的比特位設(shè)置來解讀的。從這個(gè)意義上講,variant字段更準(zhǔn)確地說可以被稱作類型字段;然而為了與歷史文檔兼容,仍沿用“variant”這一術(shù)語(yǔ)。
下表列出了variant字段可能的內(nèi)容,其中字母"x"表示無關(guān)緊要或不關(guān)心的值:
- Msb0(最高有效位0):此為最高位。
- Msb1:次高位。
- Msb2:第三高位。
Msb0 | Msb1 | Msb2 | 描述 |
0 | x | x | 保留,用于NCS(Network Computing System)向后兼容 |
1 | 0 | x | 此文檔中指定的variant變體 |
1 | 1 | 0 | 保留,用于微軟公司系統(tǒng)的向后兼容 |
1 | 1 | 1 | 保留供未來定義 |
類型(變體)的標(biāo)志符可以是 2位也可是3位,本文圍繞的的是 RFC4122: A Universally Unique IDentifier (UUID) URN Namespace[4] 類型(變體), 即上面表格的第二行,其第三高位 為 x,表示該值并無意義,所以該版本只需要10 即可。
10開頭的 hexDigit 十六進(jìn)制數(shù)字字符,其只有四個(gè)值。
0b1000 => 8
0b1001 => 9
0b1010 => a
0b1011 => b
用簡(jiǎn)單的圖示表示,就是 下面y的部分只會(huì)是這 四個(gè)值 8, 9, a, b其中的某個(gè)值。xxxxxxxx-xxxx-xxxx-yxxx-xxxxxxxxxxxx
簡(jiǎn)單測(cè)一測(cè),
圖片
所以呢,一個(gè)RFC4122[5]版本的 UUID正宗不正宗,這么驗(yàn)證也是一種手段。
版本(子類型)
上面提到了UUID的類型(變體), 而這里版本,可以理解為某個(gè)類型(變體)下的不同子類型。當(dāng)然本文討論的是 變體 10 即RFC4122[6] 下的版本(子類型)。UUID的類型(變體)有字段標(biāo)記,當(dāng)然 這里的版本也有。
即版本號(hào)time_hi_and_version 的第12至15位
圖片
V4版本如下:
圖片
一共有5個(gè)版本:
圖片
用簡(jiǎn)單的圖示表示,就是 下面V的部分只會(huì)是這 五個(gè)值 1, 2, 3, 4, 5其中的某個(gè)值。xxxxxxxx-xxxx-V xxx-yxxx-xxxxxxxxxxxx
借用uuid 庫(kù)演示一下:
圖片
時(shí)間戳
先回顧一下兩張圖
圖片
image.png
圖片
第一張是UUID 各部分的組成,time_low ,time_mid, time_hi_and_version 包含了時(shí)間戳的不同部分。
第二張是UUID的五個(gè)版本,但是只有 V1 和 V2 提到了時(shí)間戳,也確實(shí)是這樣,除了V1和V2版本真正用了時(shí)間戳,其余版本通過不同手段生成了數(shù)據(jù)填充了time_low ,time_mid, time_hi_and_version 這三個(gè)部分。
那這個(gè)時(shí)間戳 是 開發(fā)者們 常用的 Date.now() 這個(gè)時(shí)間戳嘛, 答案當(dāng)然不是。
這里的時(shí)間戳是一個(gè)60位長(zhǎng)度的數(shù)值。對(duì)于UUID版本1和2,它通過協(xié)調(diào)世界時(shí)(UTC)表示,即從1582年10月15日0點(diǎn)0分0秒開始算起的100納秒間隔計(jì)數(shù)。
比如 2024年1月1日0時(shí)0分0秒,這個(gè)值時(shí)間戳怎么算呢
const startOfUuidEpoch = new Date('1582-10-15T00:00:00.000Z');
const uuidTimestampFromDate = (date) => {
// 直接計(jì)算給定日期距離UUID紀(jì)元開始的毫秒數(shù)
const msSinceUuidEpoch = date.getTime() - startOfUuidEpoch.getTime();
// 將毫秒轉(zhuǎn)換為100納秒的整數(shù)倍, 1 毫秒=1000000 納秒
const uuidTimestampIn100Ns = Math.floor(msSinceUuidEpoch * 10000); // 每毫秒乘以10,000得到100納秒
return uuidTimestampIn100Ns;
};
// 計(jì)算2024年1月1日對(duì)應(yīng)的UUID V1版本時(shí)間戳
const targetDate = new Date('2024-01-01T00:00:00.000Z');
const uuidV1Timestamp = uuidTimestampFromDate(targetDate);
// 139233600000000000
要保存為60位, 并劃分高位(12),中間(16),低位三部分(32)
uuidV1Timestamp.toString(2).padStart(60,'0')
// 000111101110101010000011100010110100110011001000000000000000
time-high time-mid time-low
000111101110 1010100000111000 10110100110011001000000000000000
在不具備UTC功能但擁有本地時(shí)間的系統(tǒng)中,只要在整個(gè)系統(tǒng)內(nèi)保持一致,也可以使用本地時(shí)間替代UTC。然而,這種方法并不推薦,因?yàn)閮H需要一個(gè)時(shí)區(qū)偏移量即可從本地時(shí)間生成UTC時(shí)間。
對(duì)于UUID版本3或5,時(shí)間戳是一個(gè)根據(jù)4.3 Algorithm for Creating a Name-Based UUID[7],由名稱構(gòu)建的60位值, V3和V5 區(qū)別是在算法上。
而對(duì)于UUID版本4,時(shí)間戳則是一個(gè)隨機(jī)或偽隨機(jī)生成的60位值,具體細(xì)節(jié)參見第4.4 Algorithms for Creating a UUID from Truly Random or Pseudo-Random Numbers[8]
小結(jié)一下,
- 時(shí)間戳是即從1582年10月15日0點(diǎn)0分0秒開始算起的100納秒間隔計(jì)數(shù),是一個(gè)60位值,被分為 高位,中間,低位三部分填充到UUID中。
- 只有V1和V2 真正意義上用了時(shí)間戳
- V3和V5 由名字構(gòu)建而成的60位值
- V4隨機(jī)或偽隨機(jī)生成的60位值
時(shí)鐘序列
時(shí)鐘序列(clock sequence)用于幫助避免因系統(tǒng)時(shí)間被設(shè)置回溯或節(jié)點(diǎn)ID發(fā)生變化時(shí)可能出現(xiàn)的重復(fù)標(biāo)識(shí)符。
舉個(gè)實(shí)例,手動(dòng)把系統(tǒng)的時(shí)間設(shè)置為一個(gè)過去的時(shí)間,那么就可能導(dǎo)致生成重復(fù)的UUID.
協(xié)議考慮到了這點(diǎn),就增加了時(shí)鐘序列,增加一個(gè)變數(shù),讓結(jié)果不一樣,當(dāng)然如果序列也是不變的,那么還是可能重復(fù),所以這個(gè)時(shí)鐘序列也是會(huì)變化的。
如果系統(tǒng)時(shí)鐘被設(shè)置為向前的時(shí)間點(diǎn)之前,或者可能已經(jīng)回溯(例如,在系統(tǒng)關(guān)機(jī)期間),并且UUID生成器無法確定在此期間沒有生成時(shí)間戳更大的UUID,則需要更改時(shí)鐘序列。若已知先前時(shí)鐘序列的值,可以直接遞增;否則應(yīng)將其設(shè)置為一個(gè)隨機(jī)或高質(zhì)量的偽隨機(jī)值。
同樣,當(dāng)節(jié)點(diǎn)ID發(fā)生變化(比如因?yàn)榫W(wǎng)絡(luò)適配器在不同機(jī)器間移動(dòng)),將時(shí)鐘序列設(shè)置為隨機(jī)數(shù)可以最大限度地降低由于各機(jī)器之間微小時(shí)間設(shè)置差異導(dǎo)致重復(fù)UUID的可能性。盡管理論上知道與變更后的節(jié)點(diǎn)ID關(guān)聯(lián)的時(shí)鐘序列值后可以直接遞增,但這種情況在實(shí)際操作中往往難以實(shí)現(xiàn)。
時(shí)鐘序列必須在其生命周期內(nèi)首次初始化為隨機(jī)數(shù),以減少跨系統(tǒng)間的關(guān)聯(lián)性。這提供了最大程度的保護(hù),防止可能會(huì)快速在系統(tǒng)間遷移或切換的節(jié)點(diǎn)標(biāo)識(shí)符產(chǎn)生問題。初始值不應(yīng)與節(jié)點(diǎn)標(biāo)識(shí)符相關(guān)聯(lián)。
同樣的,這個(gè)時(shí)間序列只在 V1和V2 是真的按照上面的規(guī)則或者約定來執(zhí)行的。
對(duì)于UUID版本3或5,時(shí)鐘序列是一個(gè)由第4.3 Algorithm for Creating a Name-Based UUID[9]節(jié)描述的名稱構(gòu)建的14位值。
而對(duì)于UUID版本4,時(shí)鐘序列則是一個(gè)如第4.4 Algorithms for Creating a UUID from Truly Random or Pseudo-Random Numbers[10]節(jié)所述隨機(jī)或偽隨機(jī)生成的14位值。
節(jié)點(diǎn)標(biāo)志符
空間唯一節(jié)點(diǎn)標(biāo)識(shí)符,用來確保即便在同一時(shí)間生成的UUID也能在特定網(wǎng)絡(luò)或物理位置上保持唯一性。
對(duì)于UUID V1,這個(gè)節(jié)點(diǎn)標(biāo)識(shí)符通常基于網(wǎng)絡(luò)適配器的MAC地址或者在沒有硬件MAC地址可用時(shí)由系統(tǒng)自動(dòng)生成一個(gè)偽隨機(jī)數(shù)。它的目的是反映生成UUID的設(shè)備在網(wǎng)絡(luò)或物理空間中的唯一性,即使在相同的時(shí)序和時(shí)鐘序列條件下,不同的設(shè)備也會(huì)因?yàn)槠洫?dú)特的節(jié)點(diǎn)標(biāo)識(shí)符而產(chǎn)生不同的UUID。
在UUID V2中,雖然不常用,但節(jié)點(diǎn)標(biāo)識(shí)符的概念同樣適用,用于標(biāo)識(shí)系統(tǒng)的唯一性,只不過這里的“空間”更多地指向組織結(jié)構(gòu)或其他邏輯意義上的空間劃分。
總之,空間唯一節(jié)點(diǎn)標(biāo)識(shí)符是為了保證在分布式系統(tǒng)環(huán)境下,即使時(shí)間戳相同的情況下也能生成唯一的UUID,以區(qū)分不同物理節(jié)點(diǎn)上的事件或資源。
對(duì)于UUID版本3或5: 節(jié)點(diǎn)字段(48位)是根據(jù)第4.3節(jié)描述的方法,從一個(gè)名稱構(gòu)造而來。對(duì)于UUID版本4: 節(jié)點(diǎn)字段(同樣是48位)是一個(gè)隨機(jī)或偽隨機(jī)生成的值。
小結(jié)
從V1和V2版本來看, UUID最后是想通過 時(shí)間和空間 上兩層手段保證其唯一性:
- 時(shí)間:時(shí)間戳 + 時(shí)鐘時(shí)序
- 空間:節(jié)點(diǎn)標(biāo)志符(比如MAC地址)
同時(shí)考慮了 類型(變體) 和 版本(子類型),即下面這些組信息組成了UUID
- 時(shí)間戳
- 時(shí)鐘序列
- 節(jié)點(diǎn)標(biāo)志符
- 保留位:即類型(變體)信息
- 版本:V1到V5
因?yàn)楸A粑缓桶姹拘畔⒈旧硎枪潭ǖ?,是可以從最后?2位16進(jìn)制字符是可以直接或者間接看到的。
再回顧這張圖,是不是比較清晰了
圖片
UUID 的 生成
協(xié)議中有具體描述V1, V3和V5, 以及V4的基本流程或者約束。
v4
瀏覽器和nodejs內(nèi)置的了V4的生成函數(shù), 而且其生成規(guī)則相對(duì)簡(jiǎn)單。對(duì)應(yīng)著協(xié)議 4.4. Algorithms for Creating a UUID from Truly Random or Pseudo-Random Numbers[11]。
版本4的UUID旨在通過真正的隨機(jī)數(shù)或偽隨機(jī)數(shù)生成UUID。其生成算法相對(duì)簡(jiǎn)單,主要依賴于隨機(jī)性:
生成算法步驟如下:
- 在UUID結(jié)構(gòu)中的clock_seq_hi_and_reserved部分,將最高兩位有效位(即第6位和第7位)分別設(shè)置為0和1。
- 在UUID結(jié)構(gòu)中的time_hi_and_version字段,將最高四位有效位(即第12位至第15位)設(shè)置為來自第 4.1.3節(jié)[12] 的4位版本號(hào),對(duì)于版本4 UUID,這個(gè)版本號(hào)是固定的0100。
- 將除了以上已設(shè)定的位之外的所有其他位設(shè)置為隨機(jī)(或偽隨機(jī))選取的值。
不好理解,就看這張圖:
圖片
關(guān)于隨機(jī)性安全要求, 引用了BCP 106[13]標(biāo)準(zhǔn)文檔,即 RFC 4086[14]。RFC 4086是一份由IETF制定的最佳當(dāng)前實(shí)踐(Best Current Practice, BCP)文檔,其標(biāo)題為“Security Requirements for Randomness”,該文檔詳細(xì)闡述了在實(shí)現(xiàn)安全協(xié)議與系統(tǒng)時(shí)所需的隨機(jī)數(shù)生成器的要求和特性,確保生成的隨機(jī)數(shù)具有足夠的不可預(yù)測(cè)性和熵,能滿足各類安全應(yīng)用,包括但不限于密碼學(xué)應(yīng)用中的隨機(jī)性需求。
總之,生成版本4 UUID的過程中,首先對(duì)特定字段的幾位進(jìn)行固定設(shè)置以標(biāo)明版本和時(shí)鐘序列特征,然后其余所有位均通過隨機(jī)或偽隨機(jī)過程填充數(shù)值,以此確保生成的UUID具備全球唯一性和較強(qiáng)的隨機(jī)性。
至于v2怎么生成,協(xié)議貌似沒有提到, v1 , v3 和 v5均有提到,這邊就直接翻譯過來,有興趣的可以看看大致邏輯。不敢興趣的直接跳到后續(xù)章節(jié)
V1
對(duì)應(yīng)這協(xié)議 4.2.2. Generation Details[15] ,按照以下步驟生成的:
- 確定時(shí)間戳和時(shí)鐘序列:遵循第 4.2.1 節(jié)描述的方法,獲取基于 UTC 的時(shí)間戳以及用于 UUID 的時(shí)鐘序列。
- 處理時(shí)間戳和時(shí)鐘序列:將時(shí)間戳視為一個(gè) 60 位無符號(hào)整數(shù),時(shí)鐘序列視為一個(gè) 14 位無符號(hào)整數(shù),并按順序編號(hào)每個(gè)字段中的位,最低有效位從0開始計(jì)數(shù)。
- 設(shè)置時(shí)間低位字段(time_low field):將其設(shè)置為時(shí)間戳的最低有效 32 位(位 0 到 31),保持相同的位權(quán)重順序。
- 設(shè)置時(shí)間中間字段(time_mid field):將其設(shè)置為時(shí)間戳中的位 32 到 47,同樣保持位權(quán)重順序一致。
- 設(shè)置時(shí)間高位及版本字段(time_hi_and_version field)的低 12 位(位 0 到 11):將其設(shè)置為時(shí)間戳的位 48 到 59,保持位權(quán)重順序一致。
- 設(shè)置時(shí)間高位及版本字段的高 4 位:將這 4 位(位 12 到 15)設(shè)置為對(duì)應(yīng)于所創(chuàng)建 UUID 版本的 4 位版本號(hào)。
- 設(shè)置時(shí)鐘序列低位字段(clock_seq_low field):將其設(shè)置為時(shí)鐘序列的最低有效 8 位(位 0 到 7),同樣保持位權(quán)重順序一致。
- 設(shè)置時(shí)鐘序列高位及保留字段的低 6 位(clock_seq_hi_and_reserved field 的位 0 到 5):將其設(shè)置為時(shí)鐘序列的最高有效 6 位(位 8 到 13),保持相同位權(quán)重順序。
- 設(shè)置時(shí)鐘序列高位及保留字段的高 2 位:將這 2 位(位 6 和 7)分別設(shè)置為 0 和 1,以滿足版本 1 UUID 的標(biāo)準(zhǔn)格式要求。
- 設(shè)置節(jié)點(diǎn)字段(node field):將其設(shè)置為 48 位的 IEEE MAC 地址,地址中的每一位都保持原有的位權(quán)重順序
V3 和 V5
對(duì)應(yīng)協(xié)議的 4.3. Algorithm for Creating a Name-Based UUID[16]
版本3或5的UUID設(shè)計(jì)用于從特定 命名空間(name space) 內(nèi)的且在該命名空間內(nèi)唯一的 名字(names) 生成UUID。這里的名字(names)和命名空間(name space)的概念應(yīng)該廣泛理解,不僅限于文本名稱。例如,一些命名空間包括域名系統(tǒng)(DNS)、統(tǒng)一資源定位符(URLs)、ISO對(duì)象標(biāo)識(shí)符(OIDs)、X.500區(qū)別名(DNs)以及編程語(yǔ)言中的保留字等。在這些命名空間內(nèi)分配名稱和確保其唯一性的具體機(jī)制或規(guī)則不在本規(guī)范的討論范圍內(nèi)。
對(duì)于這類UUID的要求如下:
- 在同一命名空間內(nèi),使用相同名稱在不同時(shí)間生成的UUID必須完全相同。
- 在同一命名空間內(nèi),使用兩個(gè)不同名稱生成的UUID應(yīng)當(dāng)是不同的(概率極高)。
- 在兩個(gè)不同命名空間內(nèi),使用相同名稱生成的UUID也應(yīng)當(dāng)是不同的(概率極高)。
- 如果兩個(gè)由名稱生成的UUID相同,則它們幾乎肯定是由同一命名空間內(nèi)的相同名稱生成的。
生成基于名稱和命名空間的UUID的具體算法步驟如下:
- 為給定命名空間內(nèi)所有由名稱生成的UUID分配一個(gè)作為“命名空間ID”的UUID;參見附錄C[17]中預(yù)定義的一些值。
- 選擇MD5 [4[18]] 或SHA-1 [8[19]] 其中的一種哈希算法;如果不考慮向后兼容性,建議優(yōu)先使用SHA-1。
- 將名稱轉(zhuǎn)換為其命名空間規(guī)定的標(biāo)準(zhǔn)化字節(jié)序列形式,并將命名空間ID以網(wǎng)絡(luò)字節(jié)序排列。
- 計(jì)算命名空間ID與名稱連接后的哈希值。
- 將哈希值的前四個(gè)八位組(octets 0-3)賦給時(shí)間低位字段(time_low field)的前四個(gè)八位組。
- 將哈希值的第五和第六個(gè)八位組賦給時(shí)間中間字段(time_mid field)的前兩個(gè)八位組。
- 將哈希值的第七和第八個(gè)八位組賦給時(shí)間高位及版本字段(time_hi_and_version field)的前兩個(gè)八位組。
- 將時(shí)間高位及版本字段的四位最顯著位(bit 12 至 15)設(shè)置為第4.1.3節(jié)中指定的相應(yīng)4位版本號(hào)。
- 將哈希值的第八個(gè)八位組賦給時(shí)鐘序列高位及保留字段(clock_seq_hi_and_reserved field)。
- 將時(shí)鐘序列高位及保留字段的兩位最顯著位(bit 6 和 7)分別設(shè)置為0和1。
- 將哈希值的第九個(gè)八位組賦給時(shí)鐘序列低位字段(clock_seq_low field)。
- 將哈希值的第十至第十五個(gè)八位組賦給節(jié)點(diǎn)字段(node field)的前六個(gè)八位組。
- 最后,將生成的UUID轉(zhuǎn)換成本地字節(jié)序
獲取 UUID V4
這里就只介紹V4版本,因?yàn)閂4是基于 隨機(jī)或者偽隨機(jī)來實(shí)現(xiàn)的,只要保證 保留位 和 版本號(hào) 的固定,其他的隨機(jī)生成就好。
正則 + Math.random
利用Math.random() 方法生成隨機(jī)數(shù)。
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (Math.random() * 16 | 0), v = c == 'x' ? r : (r & 0b0011 | 0b1000);
return v.toString(16);
});
}
先固定好格式,執(zhí)行replace,整體代碼不難,唯一需要提一下的是 (r & 0b0011 | 0b1000) 操作,這里的作用就是設(shè)置保留位的值10
r & 0b0011 // 高位,即2,3位 變?yōu)?00
r & 0b0011 | 0b1000 // 高位,即2,3位 變?yōu)?10
舉個(gè)例子, 用9為例,其二進(jìn)制 0b1001 &
0b1001 & 0b0011 => 0b0011
0b0011 | 0b1000 => 0b1011
crypto.randomUUID
現(xiàn)代瀏覽器也內(nèi)置 Crypto: randomUUID() method[20] , nodejs 15.6.0 版本以上就內(nèi)置了crypto.randomUUID([options])[21]
crypto.randomUUID()
// 4d93f326-3f48-4a43-929d-b6489f4754b5
URL.createObjectURL
function uuid() {
const url = URL.createObjectURL(new Blob([]));
// const uuid = url.split("/").pop();
const uid = url.substring(url.lastIndexOf('/')+ 1);
URL.revokeObjectURL(url);
return uid;
}
uuid()
// blob:http://localhost:3000/ff46f828-1570-4cc9-87af-3d600db71304
上面方式產(chǎn)生的都是 v4版本,如果v4版本滿足需求,就沒有必要去引入第三方庫(kù)了。
你是否真的需要UUID
在前端,有序后需要給數(shù)據(jù)添加一個(gè)id作為組件的key,這時(shí)候理大多數(shù)情況是不需要UUID, 也許下面的函數(shù)就滿足了你的需求。
let id = 0;
function getId () {
return id++;
}
后起之秀 NanoID
npm網(wǎng)站, NanoID是這么自我介紹的:
Nano ID 是一個(gè)精巧高效的 JavaScript 庫(kù),用于生成短小、唯一且適合放在 URL 中的標(biāo)識(shí)符字符串。這個(gè)工具提供了幾個(gè)關(guān)鍵特性:
- 體積小巧:Nano ID 的最小化和壓縮版本非常緊湊,大小僅為 116 字節(jié)。
- 安全性:該庫(kù)使用硬件隨機(jī)數(shù)生成器來確保生成的 ID 具有高安全性,可以在集群環(huán)境中安全使用。
- 短小 ID:相較于 UUID(通常包含 A-Z、a-z、0-9 以及 - 符號(hào),共 36 個(gè)字符),Nano ID 使用了更大的字符集(包括 A-Za-z0-9_-),從而將 ID 的長(zhǎng)度從 36 個(gè)符號(hào)減少到了 21 個(gè),更便于在有限空間中使用。
- 可移植性:Nano ID 已被移植到超過 20 種編程語(yǔ)言中,具有良好的跨平臺(tái)適用性。
從最新的一周的下載量來對(duì)比,首先都是絕對(duì)的熱門庫(kù),其次NanoID勢(shì)頭很盛。
截圖_20241305051331.png
借用 阿通 給的對(duì)比文案:
Nano ID 和 UUID(Universally Unique Identifier)都是用于生成唯一標(biāo)識(shí)符的機(jī)制,但它們之間存在一些關(guān)鍵差異:
- 長(zhǎng)度與格式:
UUID:標(biāo)準(zhǔn)UUID由32個(gè)十六進(jìn)制數(shù)字組成,分為5組,每組之間用短橫線-分隔,例如 xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx,總長(zhǎng)度為36個(gè)字符(包括連字符)。
Nano ID:Nano ID 可配置長(zhǎng)度,但默認(rèn)生成的是較短的字符串,通常包含21個(gè)字符,并且可以自定義字符集(默認(rèn)為 A-Za-z0-9_-)。
- 唯一性保證:
UUID:基于時(shí)間戳、MAC地址(對(duì)于v1 UUID)、隨機(jī)數(shù)(對(duì)于v4 UUID)等多種因素生成,理論上全球范圍內(nèi)幾乎不可能重復(fù)。
Nano ID:雖然也致力于生成唯一的ID,但由于其較短的長(zhǎng)度,在沒有額外存儲(chǔ)或算法保證的情況下,唯一性風(fēng)險(xiǎn)相對(duì)較大。不過,通過增大字符集和適當(dāng)增加ID長(zhǎng)度,Nano ID也能實(shí)現(xiàn)很高的唯一性概率。
應(yīng)用場(chǎng)景:
UUID:廣泛應(yīng)用于數(shù)據(jù)庫(kù)鍵、資源標(biāo)識(shí)符、網(wǎng)絡(luò)協(xié)議等需要全局唯一性的場(chǎng)景,尤其在網(wǎng)絡(luò)間不同系統(tǒng)間的交互中常見。
Nano ID:更適合于對(duì)ID長(zhǎng)度要求嚴(yán)格的場(chǎng)合,如URL友好、前端顯示或者存儲(chǔ)空間有限的情況。
性能與存儲(chǔ)成本:
UUID:由于較長(zhǎng)的字符串長(zhǎng)度,存儲(chǔ)和傳輸時(shí)可能會(huì)占用更多空間。
Nano ID:因其短小,Nano ID在存儲(chǔ)和帶寬消耗上更有優(yōu)勢(shì)。
安全性:
UUID v4 是基于強(qiáng)隨機(jī)性生成的,因此安全性較高,不易被預(yù)測(cè)。
Nano ID 也可以使用安全的隨機(jī)源生成,同樣能夠達(dá)到較高的安全性,但在默認(rèn)設(shè)置下,考慮到生成長(zhǎng)度和字符集的選擇,如果不在生成邏輯上做特殊處理以增加熵,其安全性可能不及UUID。
綜上所述,選擇Nano ID還是UUID取決于具體的應(yīng)用需求,如果重視存儲(chǔ)效率和簡(jiǎn)潔性,同時(shí)能接受合理的唯一性保證策略,則Nano ID可能更為合適;而在需要絕對(duì)唯一性和不考慮存儲(chǔ)效率的場(chǎng)景下,UUID往往是更好的選擇。
引用
RFC4122: A Universally Unique IDentifier (UUID) URN Namespace[22]UUID那些事[23]
參考資料
[1]RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace: https://www.rfc-editor.org/rfc/rfc4122
[2]4.1.2. Layout and Byte Order: https://www.rfc-editor.org/rfc/rfc4122#section-4.1.2
[3]4.1.1. Variant: https://www.rfc-editor.org/rfc/rfc4122#section-4.1.1
[4]RFC4122: A Universally Unique IDentifier (UUID) URN Namespace: https://www.rfc-editor.org/rfc/rfc4122
[5]RFC4122: https://www.rfc-editor.org/rfc/rfc4122
[6]RFC4122: https://www.rfc-editor.org/rfc/rfc4122
[7]4.3 Algorithm for Creating a Name-Based UUID: https://www.rfc-editor.org/rfc/rfc4122#section-4.3
[8]4.4 Algorithms for Creating a UUID from Truly Random or Pseudo-Random Numbers: https://www.rfc-editor.org/rfc/rfc4122#section-4.4
[9]4.3 Algorithm for Creating a Name-Based UUID: https://www.rfc-editor.org/rfc/rfc4122#section-4.3
[10]4.4 Algorithms for Creating a UUID from Truly Random or Pseudo-Random Numbers: https://www.rfc-editor.org/rfc/rfc4122#section-4.4
[11]4.4. Algorithms for Creating a UUID from Truly Random or Pseudo-Random Numbers: https://www.rfc-editor.org/rfc/rfc4122#section-4.4
[12]4.1.3節(jié): https://www.rfc-editor.org/rfc/rfc4122#section-4.1.3
[13]BCP 106: https://www.rfc-editor.org/info/bcp106
[14]RFC 4086: https://www.rfc-editor.org/rfc/rfc4086
[15]4.2.2. Generation Details: https://www.rfc-editor.org/rfc/rfc4122#section-4.2.2
[16]4.3. Algorithm for Creating a Name-Based UUID: https://www.rfc-editor.org/rfc/rfc4122#section-4.3
[17]附錄C: https://www.rfc-editor.org/rfc/rfc4122#appendix-C
[18][4: https://www.rfc-editor.org/rfc/rfc4122#ref-4
[19][8: https://www.rfc-editor.org/rfc/rfc4122#ref-8
[20]Crypto: randomUUID() method: https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID
[21]crypto.randomUUID([options]): https://nodejs.org/docs/latest/api/crypto.html#cryptorandomuuidoptions
[22]RFC4122: A Universally Unique IDentifier (UUID) URN Namespace: https://www.rfc-editor.org/rfc/rfc4122
[23]UUID那些事: https://www.cnblogs.com/yjf512/p/9045341.html