UUIDv7 是一個 128 位唯一識別符號,就像它的前輩一樣,例如廣泛使用的 UUIDv4。但與 v4 不同,UUIDv7 可以按時間排序,精度為 1 毫秒。透過結合時間戳和隨機部分,UUIDv7 成為資料庫(包括分散式資料庫)中記錄識別符號的絕佳選擇。
讓我們簡要探索一下 UUIDv7 結構,然後討論 20 種語言的零依賴實現(根據 Stack Overflow 調查排名)。
結構
UUIDv7 以字串形式表示時如下所示:
0190163d-8694-739b-aea5-966c26f8ad91 └─timestamp─┘ │└─┤ │└───rand_b─────┘ ver │var rand_a
|
這個 128 位的值由幾部分組成:
- timestamp(48 位)是以毫秒為單位的 Unix 時間戳。
- ver(4 位)是 UUID 版本(7)。
- rand_a(12 位)是隨機生成的。
- var* (2 位) 等於10。
- rand_b(62 位)是隨機生成的。
* 在字串表示法中,每個符號編碼 4 位十六進位制數,因此示例中的 a 是 1010,其中前兩位是固定變體(10),後兩位是隨機變數。因此得到的十六進位制數可以是 8 (1000)、9 (1001)、a (1010) 或 b (1011)。
細節: RFC 9652
JavaScript
用 crypto.getRandomValues()初始化一個隨機陣列,用 Date.now()獲取當前時間戳,用時間戳填充陣列,設定版本和變體。
function uuidv7() { // random bytes const value = new Uint8Array(16); crypto.getRandomValues(value);
// current timestamp in ms const timestamp = BigInt(Date.now());
// timestamp value[0] = Number((timestamp >> 40n) & 0xffn); value[1] = Number((timestamp >> 32n) & 0xffn); value[2] = Number((timestamp >> 24n) & 0xffn); value[3] = Number((timestamp >> 16n) & 0xffn); value[4] = Number((timestamp >> 8n) & 0xffn); value[5] = Number(timestamp & 0xffn);
// version and variant value[6] = (value[6] & 0x0f) | 0x70; value[8] = (value[8] & 0x3f) | 0x80;
return value; }
const uuidVal = uuidv7(); const uuidStr = Array.from(uuidVal) .map((b) => b.toString(16).padStart(2, "0")) .join(""); console.log(uuidStr);
|
TypeScript 版本相同,唯一的區別是函式簽名:
function uuidv7(): Uint8Array { // ... }
|
Python
用os.urandom() 初始化一個隨機陣列,用time.time() 獲取當前時間戳,用時間戳填充陣列,設定版本和變體。
import os import time
def uuidv7(): # random bytes value = bytearray(os.urandom(16))
# current timestamp in ms timestamp = int(time.time() * 1000)
# timestamp value[0] = (timestamp >> 40) & 0xFF value[1] = (timestamp >> 32) & 0xFF value[2] = (timestamp >> 24) & 0xFF value[3] = (timestamp >> 16) & 0xFF value[4] = (timestamp >> 8) & 0xFF value[5] = timestamp & 0xFF
# version and variant value[6] = (value[6] & 0x0F) | 0x70 value[8] = (value[8] & 0x3F) | 0x80
return value
if __name__ == "__main__": uuid_val = uuidv7() print(''.join(f'{byte:02x}' for byte in uuid_val))
|
SQL
使用 strftime() (SQLite) 或 now() (PostgreSQL) 獲取當前時間戳部分,使用 random() 獲取隨機部分,然後將所有內容連線成 UUID 字串。
SQLite (by Fabio Lima):
select -- timestamp format('%08x', ((strftime('%s') * 1000) >> 16)) || '-' || format('%04x', ((strftime('%s') * 1000) + ((strftime('%f') * 1000) % 1000)) & 0xffff) || '-' || -- version / rand_a format('%04x', 0x7000 + abs(random()) % 0x0fff) || '-' || -- variant / rand_b format('%04x', 0x8000 + abs(random()) % 0x3fff) || '-' || -- rand_b format('%012x', abs(random()) >> 16) as value;
|
PostgreSQL:
select -- timestamp lpad(to_hex(((extract(epoch from now()) * 1000)::bigint >> 16)), 8, '0') || '-' || lpad(to_hex(((extract(epoch from now()) * 1000 + (date_part('milliseconds', now())::bigint % 1000))::bigint & 0xffff)), 4, '0') || '-' || -- version / rand_a lpad(to_hex((0x7000 + (random() * 0x0fff)::int)), 4, '0') || '-' || -- variant / rand_b lpad(to_hex((0x8000 + (random() * 0x3fff)::int)), 4, '0') || '-' || -- rand_b lpad(to_hex((floor(random() * (2^48))::bigint >> 16)), 12, '0') AS value;
|
Shell
用 /dev/urandom 初始化隨機位元組字串,用日期獲取當前時間戳,用時間戳和位元組字串填充陣列,設定版本和變數。
#!/bin/sh
uuidv7() { # random bytes rand_bytes=$(dd if=/dev/urandom bs=1 count=16 2>/dev/null | xxd -p)
# current timestamp in ms timestamp=$(date +%s%3N) t_hex=$(printf "%012x" $timestamp)
# timestamp value[0]=${t_hex:0:2} value[1]=${t_hex:2:2} value[2]=${t_hex:4:2} value[3]=${t_hex:6:2} value[4]=${t_hex:8:2} value[5]=${t_hex:10:2}
# version / rand_a value[6]=$(printf "%02x" $((0x70 | (0x${rand_bytes:12:2} & 0x0F)))) value[7]=${rand_bytes:14:2}
# variant / rand_b value[8]=$(printf "%02x" $((0x80 | (0x${rand_bytes:16:2} & 0x3F))))
# rand_b value[9]=${rand_bytes:18:2} value[10]=${rand_bytes:20:2} value[11]=${rand_bytes:22:2} value[12]=${rand_bytes:24:2} value[13]=${rand_bytes:26:2} value[14]=${rand_bytes:28:2} value[15]=${rand_bytes:30:2}
echo "${value[@]}" }
for byte in $(uuidv7); do printf "%s" "$byte" done echo
|
Java
使用 SecureRandom.nextBytes() 初始化隨機陣列,使用 System.currentTimeMillis() 獲取當前時間戳,根據時間戳填充陣列,設定版本和變數。
import java.nio.ByteBuffer; import java.security.SecureRandom; import java.util.UUID;
public class UUIDv7 { private static final SecureRandom random = new SecureRandom();
public static UUID randomUUID() { byte[] value = randomBytes(); ByteBuffer buf = ByteBuffer.wrap(value); long high = buf.getLong(); long low = buf.getLong(); return new UUID(high, low); }
public static byte[] randomBytes() { // random bytes byte[] value = new byte[16]; random.nextBytes(value);
// current timestamp in ms long timestamp = System.currentTimeMillis();
// timestamp value[0] = (byte) ((timestamp >> 40) & 0xFF); value[1] = (byte) ((timestamp >> 32) & 0xFF); value[2] = (byte) ((timestamp >> 24) & 0xFF); value[3] = (byte) ((timestamp >> 16) & 0xFF); value[4] = (byte) ((timestamp >> 8) & 0xFF); value[5] = (byte) (timestamp & 0xFF);
// version and variant value[6] = (byte) ((value[6] & 0x0F) | 0x70); value[8] = (byte) ((value[8] & 0x3F) | 0x80);
return value; }
public static void main(String[] args) { var uuid = UUIDv7.randomUUID(); System.out.println(uuid); } }
|
by David Ankin
C#
使用 RandomNumberGenerator.GetBytes() 初始化隨機陣列,使用 DateTimeOffset.UtcNow 獲取當前時間戳,根據時間戳填充陣列,設定版本和變數。
using System; using System.Security.Cryptography;
public class UUIDv7 { private static readonly RandomNumberGenerator random = RandomNumberGenerator.Create();
public static byte[] Generate() { // random bytes byte[] value = new byte[16]; random.GetBytes(value);
// current timestamp in ms long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
// timestamp value[0] = (byte)((timestamp >> 40) & 0xFF); value[1] = (byte)((timestamp >> 32) & 0xFF); value[2] = (byte)((timestamp >> 24) & 0xFF); value[3] = (byte)((timestamp >> 16) & 0xFF); value[4] = (byte)((timestamp >> 8) & 0xFF); value[5] = (byte)(timestamp & 0xFF);
// version and variant value[6] = (byte)((value[6] & 0x0F) | 0x70); value[8] = (byte)((value[8] & 0x3F) | 0x80);
return value; }
public static void Main(string[] args) { byte[] uuidVal = Generate(); foreach (byte b in uuidVal) { Console.Write("{0:x2}", b); } Console.WriteLine(); } }
|
C++
使用 random_device 初始化隨機陣列,使用 system_clock.time_since_epoch()獲取當前時間戳,根據時間戳填充陣列,設定版本和變數。
include <array> include <chrono> include <cstdint> include <cstdio> include <random>
std::array<uint8_t, 16> uuidv7() { // random bytes std::random_device rd; std::array<uint8_t, 16> random_bytes; std::generate(random_bytes.begin(), random_bytes.end(), std::ref(rd)); std::array<uint8_t, 16> value; std::copy(random_bytes.begin(), random_bytes.end(), value.begin());
// current timestamp in ms auto now = std::chrono::system_clock::now(); auto millis = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch() ).count();
// timestamp value[0] = (millis >> 40) & 0xFF; value[1] = (millis >> 32) & 0xFF; value[2] = (millis >> 24) & 0xFF; value[3] = (millis >> 16) & 0xFF; value[4] = (millis >> 8) & 0xFF; value[5] = millis & 0xFF;
// version and variant value[6] = (value[6] & 0x0F) | 0x70; value[8] = (value[8] & 0x3F) | 0x80;
return value; }
int main() { auto uuid_val = uuidv7(); for (const auto& byte : uuid_val) { printf("%02x", byte); } printf("\n"); return 0; }
|
C語言
使用 getentropy() 初始化隨機陣列,使用 timespec_get()獲取當前時間戳,根據時間戳填充陣列,設定版本和變數。
include <stdio.h> include <stdlib.h> include <stdint.h> include <time.h> include <unistd.h>
int uuidv7(uint8_t* value) { // random bytes int err = getentropy(value, 16); if (err != EXIT_SUCCESS) { return EXIT_FAILURE; }
// current timestamp in ms struct timespec ts; int ok = timespec_get(&ts, TIME_UTC); if (ok == 0) { return EXIT_FAILURE; } uint64_t timestamp = (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
// timestamp value[0] = (timestamp >> 40) & 0xFF; value[1] = (timestamp >> 32) & 0xFF; value[2] = (timestamp >> 24) & 0xFF; value[3] = (timestamp >> 16) & 0xFF; value[4] = (timestamp >> 8) & 0xFF; value[5] = timestamp & 0xFF;
// version and variant value[6] = (value[6] & 0x0F) | 0x70; value[8] = (value[8] & 0x3F) | 0x80;
return EXIT_SUCCESS; }
int main() { uint8_t uuid_val[16]; uuidv7(uuid_val); for (size_t i = 0; i < 16; i++) { printf("%02x", uuid_val[i]); } printf("\n"); }
|
PHP
使用 random_bytes() 初始化隨機字串,使用 microtime() 獲取當前時間戳,從時間戳中填充字元,設定版本和變數。
<?php function uuidv7() { // random bytes $value = random_bytes(16);
// current timestamp in ms $timestamp = intval(microtime(true) * 1000);
// timestamp $value[0] = chr(($timestamp >> 40) & 0xFF); $value[1] = chr(($timestamp >> 32) & 0xFF); $value[2] = chr(($timestamp >> 24) & 0xFF); $value[3] = chr(($timestamp >> 16) & 0xFF); $value[4] = chr(($timestamp >> 8) & 0xFF); $value[5] = chr($timestamp & 0xFF);
// version and variant $value[6] = chr((ord($value[6]) & 0x0F) | 0x70); $value[8] = chr((ord($value[8]) & 0x3F) | 0x80);
return $value; }
$uuid_val = uuidv7(); echo bin2hex($uuid_val);
|
Go
使用 rand.Read() 初始化隨機陣列,使用 time.Now() 獲取當前時間戳,根據時間戳填充陣列,設定版本和變數。
package main
import ( "crypto/rand" "fmt" "time" )
func uuidv7() ([16]byte, error) { // random bytes var value [16]byte _, err := rand.Read(value[:]) if err != nil { return value, err }
// current timestamp in ms timestamp := uint64(time.Now().UnixNano() / int64(time.Millisecond))
// timestamp value[0] = byte((timestamp >> 40) & 0xFF) value[1] = byte((timestamp >> 32) & 0xFF) value[2] = byte((timestamp >> 24) & 0xFF) value[3] = byte((timestamp >> 16) & 0xFF) value[4] = byte((timestamp >> 8) & 0xFF) value[5] = byte(timestamp & 0xFF)
// version and variant value[6] = (value[6] & 0x0F) | 0x70 value[8] = (value[8] & 0x3F) | 0x80
return value, nil }
func main() { uuidVal, err := uuidv7() if err != nil { panic(err) } for _, byte := range uuidVal { fmt.Printf("%02x", byte) } fmt.Println() }
|
Rust
使用 Rng.fill() 初始化隨機陣列,使用 SystemTime::now() 獲取當前時間戳,根據時間戳填充陣列,設定版本和變數。
use rand::Rng; use std::time::{SystemTime, UNIX_EPOCH}; use std::error::Error;
fn uuidv7() -> Result<[u8; 16], Box<dyn Error>> { // random bytes let mut value = [0u8; 16]; rand::thread_rng().fill(&mut value);
// current timestamp in ms let timestamp = match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(duration) => duration.as_millis() as u64, Err(_) => return Err(Box::from("Failed to get system time")), };
// timestamp value[0] = (timestamp >> 40) as u8; value[1] = (timestamp >> 32) as u8; value[2] = (timestamp >> 24) as u8; value[3] = (timestamp >> 16) as u8; value[4] = (timestamp >> 8) as u8; value[5] = timestamp as u8;
// version and variant value[6] = (value[6] & 0x0F) | 0x70; value[8] = (value[8] & 0x3F) | 0x80;
Ok(value) }
fn main() { match uuidv7() { Ok(uuid_val) => { for byte in &uuid_val { print!("{:02x}", byte); } println!(); }, Err(e) => eprintln!("Error: {}", e), } }
|
Kotlin
使用 SecureRandom.nextBytes() 初始化隨機陣列,使用 Instant.now() 獲取當前時間戳,根據時間戳填充陣列,設定版本和變數。
import java.security.SecureRandom import java.time.Instant
object UUIDv7 { private val random = SecureRandom()
fun generate(): ByteArray { // random bytes val value = ByteArray(16) random.nextBytes(value)
// current timestamp in ms val timestamp = Instant.now().toEpochMilli()
// timestamp value[0] = ((timestamp shr 40) and 0xFF).toByte() value[1] = ((timestamp shr 32) and 0xFF).toByte() value[2] = ((timestamp shr 24) and 0xFF).toByte() value[3] = ((timestamp shr 16) and 0xFF).toByte() value[4] = ((timestamp shr 8) and 0xFF).toByte() value[5] = (timestamp and 0xFF).toByte()
// version and variant value[6] = (value[6].toInt() and 0x0F or 0x70).toByte() value[8] = (value[8].toInt() and 0x3F or 0x80).toByte()
return value }
@JvmStatic fun main(args: Array<String>) { val uuidVal = generate() uuidVal.forEach { b -> print("%02x".format(b)) } println() } }
|
Ruby
使用 SecureRandom.random_bytes() 初始化隨機陣列,使用 Time.now 獲取當前時間戳,根據時間戳填充陣列,設定版本和變數。
require 'securerandom' require 'time'
def uuidv7 # random bytes value = SecureRandom.random_bytes(16).bytes
# current timestamp in ms timestamp = (Time.now.to_f * 1000).to_i
# timestamp value[0] = (timestamp >> 40) & 0xFF value[1] = (timestamp >> 32) & 0xFF value[2] = (timestamp >> 24) & 0xFF value[3] = (timestamp >> 16) & 0xFF value[4] = (timestamp >> 8) & 0xFF value[5] = timestamp & 0xFF
# version and variant value[6] = (value[6] & 0x0F) | 0x70 value[8] = (value[8] & 0x3F) | 0x80
value end
if __FILE__ == $0 uuid_val = uuidv7 puts uuid_val.pack('C*').unpack1('H*') end
|
Lua
使用 math.random() 初始化隨機表,使用 os.time() 獲取當前時間戳,根據時間戳填充列表,設定版本和變數。
local function uuidv7() -- random bytes local value = {} for i = 1, 16 do value[i] = math.random(0, 255) end
-- current timestamp in ms local timestamp = os.time() * 1000
-- timestamp value[1] = (timestamp >> 40) & 0xFF value[2] = (timestamp >> 32) & 0xFF value[3] = (timestamp >> 24) & 0xFF value[4] = (timestamp >> 16) & 0xFF value[5] = (timestamp >> 8) & 0xFF value[6] = timestamp & 0xFF
-- version and variant value[7] = (value[7] & 0x0F) | 0x70 value[9] = (value[9] & 0x3F) | 0x80
return value end
local uuid_val = uuidv7() for i = 1, uuid_val do io.write(string.format('%02x', uuid_val[i])) end print()
|
Swift
使用 UInt8.random() 初始化隨機陣列,使用 Date() .timeIntervalSince1970 獲取當前時間戳,根據時間戳填充陣列,設定版本和變數。
import Foundation
func uuidv7() -> [UInt8] { // random bytes var value = (0..<16).map { _ in UInt8.random(in: 0...255) }
// current timestamp in ms let timestamp = Int(Date().timeIntervalSince1970 * 1000)
// timestamp value[0] = UInt8((timestamp >> 40) & 0xFF) value[1] = UInt8((timestamp >> 32) & 0xFF) value[2] = UInt8((timestamp >> 24) & 0xFF) value[3] = UInt8((timestamp >> 16) & 0xFF) value[4] = UInt8((timestamp >> 8) & 0xFF) value[5] = UInt8(timestamp & 0xFF)
// version and variant value[6] = (value[6] & 0x0F) | 0x70 value[8] = (value[8] & 0x3F) | 0x80
return value }
let uuidVal = uuidv7() print(uuidVal.map { String(format: "%02x", $0) }.joined())
|