好笑,你居然不知道 Java 如何生成 UUID

先看再點贊,給本身一點思考的時間,微信搜索【 沉默王二】關注這個靠才華苟且的程序員。
本文  GitHub  github.com/itwanger 已收錄,裏面還有一線大廠整理的面試題,以及個人系列文章。

一個調皮的讀者在以前我寫的「我去」系列文章裏留言調侃說,「二哥,你是無中生小王嗎?」不不不,其實真不是的,小王是真實存在的,他一直和我並肩做戰,不辭辛勞,讓我既愛又恨。我愛他,由於他兢兢業業,不辭辛苦,和我心有靈犀;我恨他,由於他時不時會中二一下,問我一些好笑的問題,好比說此次,「二哥,你能給我說說 Java 如何生成 UUID 嗎?」java

UUID,全名叫作 Universally Unique Identifier,也就是通用惟一標識符的意思。有時候,也叫作全局惟一標識符,英文全名叫作 Globally Unique Identifier,簡拼爲 GUID。git

來看一下 UUID 的格式:程序員

123e4567-e89b-12d3-a456-556642440000
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

由四個中劃線「-」隔開,第一部分的長度爲 8,第二部分和第三部分的長度爲 4,第四部分的長度爲 12,總長度爲 36,是固定的。每一部分都是一個十六進制的數字,注意並非隨機的任意字母+數字的字符串。github

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 的取值是 八、九、a、b 中的一個。session

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 包下。

package java.util;
public final class UUID implements java.io.Serializable, Comparable<UUID> {
}

該類只有一個構造方法:

public UUID(long mostSigBits, long leastSigBits) {
    this.mostSigBits = mostSigBits;
    this.leastSigBits = leastSigBits;
}

要使用構造方法建立 UUID 對象的話,就須要傳遞兩個參數,long 型的最高位 UUID 和最低位的 UUID。

long msb = System.currentTimeMillis();
long lsb = System.currentTimeMillis();
UUID uuidConstructor = new UUID(msb, lsb);
System.out.println("UUID : "+uuidConstructor);

輸出結果以下所示:

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

UUID 類提供了一個靜態方法 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);

程序輸出結果以下所示:

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

除此以外,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);
}

nameUUIDFromBytes() 會生成一個版本 3 的 UUID,不過須要傳遞一個名稱的字節數組做爲參數。

示例以下:

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

程序輸出結果以下所示:

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);

程序輸出結果以下所示:

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 沉默王二,一枚有趣的程序員
 */
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。

來看一下輸出結果:

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。

相關文章
相關標籤/搜索