# 在 Java 中实现只有数组和字母的 UUID

**URL:** <https://www.isharkfly.com/t/java-uuid/15097>\
**Category:** 程序语言\
**Tags:** java\
**Created:** [November 3, 2023, 6:02pm UTC](https://www.isharkfly.com/t/java-uuid/15097 "2023-11-03T18:02:07Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![honeymoose](https://www.isharkfly.com/user_avatar/www.isharkfly.com/honeymoose/32/20796_2.png) [@honeymoose](https://www.isharkfly.com/u/honeymoose)\
**Post date:** [November 3, 2023, 6:02pm UTC](https://www.isharkfly.com/t/java-uuid/15097/1 "2023-11-03T18:02:07Z")

</div>

**通用唯一识别码** （英语： **U** niversally **U** nique **Id** entifier，缩写： **UUID** ）是用于计算机体系中以识别信息的一个128位标识符。

UUID 按照标准方法生成时，在实际应用中具有唯一性，且不依赖中央机构的注册和分配。UUID重复的概率接近零，可以忽略不计。

可以这样理解的是，UUID 就是一个可以在任何计算机中生成的一个识别码，这个识别码可以做到全球唯一，用来标识你的数据。

在本文章中，我们对 UUID 在 Java 中是如何实现来进行一些说明和解读。

## 结构

在对 UUID 进行说明之前，我们来看一个标准的 UUID。

下面就是一个标准的 UUID，使用横杠分隔符来进行分隔：

```auto
123e4567-e89b-42d3-a456-556642440000
xxxxxxxx-xxxx-Bxxx-Axxx-xxxxxxxxxxxx

```

标准的 UUID 目前是使用 32 个十六进制的字符来表示的。如果你对计算机中的 16 进制如何表示还不是非常熟悉的话，可以自行脑补下。

标准 UUID 使用横杠被分隔成了 5 组，分别为 8-4-4-4-12 个字符。总共这里有 32 个字符。因为我们使用了横杠，加上 4 个横杠后一共有 36 个字符。

_Nil UUID_ 是一个特殊的 UUID，这个 UUID 中的所有字符都是 0，其值为：

```auto
 00000000-0000-0000-0000-000000000000

```

针对 UUID 上面的结构，每一位都会有具体的意义：

 ![2023-11-03_14-24-39](https://cdn.isharkfly.com/com-isharkfly-www/discourse-uploads/original/2X/e/ed28cfeb8a97eefd901be10ca3de9b6b508f34ab.png)

具体的分配如下图：

 ![2023-11-03_14-25-09](https://cdn.isharkfly.com/com-isharkfly-www/discourse-uploads/original/2X/b/b9a4bd13975da6443e56bc0290206f7a49fb8e41.png)

对大部分人来说，我们值需要了解下就可以了，因为我们多会使用 API 来进行生成，如果我们不在这里说明的话，在后面我们说版本的时候就比较难理解。

### 变形

版本号在时间戳的最高 4 位（time\_hi\_and\_version 的 4-7 bit）

UUID 的变体规则如下：

 ![2023-11-03_14-26-31](https://cdn.isharkfly.com/com-isharkfly-www/discourse-uploads/original/2X/9/961420baafd26f25c357cab6afe787ca0f6cf8c3.png)

```auto
  MSB1 MSB2 MSB3
   0 X X reserved (0)
   1 0 X current variant (2)
   1 1 0 reserved for Microsoft (6)
   1 1 1 reserved for future (7)

```

针对微软的版本，UUID 还单独为微软保留了一个版本，这就可以说明为什么在 Azure 的平台上，微软那么喜欢用 UUID。

### 版本

在实际使用的时候，我们对 UUID 的版本其实并不关心。

目前大部分情况下使用默认的版本进行编程就可以了。

1. Version 1 （基于时间）：这个版本是基于随机数的，使用的基数为每 100 纳秒为一个单位，时间的起点为1582年10月15日。同时还需要加上当前计算机的网卡物理地址（MAC）。
2. Version 2 （DCE – 分布式计算机环境）: `UUID-v2` 和`V1` 很类似，是根据标识符（通常是组或用户`ID` ）、时间和节点`ID` 生成，不过区别在于`V2` 将`V1` 中的部分时间信息换成了主机名， 故应用具有局限性（有隐私风险），未大规模使用。
3. Version 3 （基于命名）： `UUID-v3` 通过散列（`MD5` ）名字空间（`namespace` ）标识符和名称生成。和`V1` 、`V2` 不同，`V3` 不依赖与机器信息和时间信息， 但是`V3` 要求输入命名空间+名称，命名空间本身也是一个`UUID` ，用来标识应用环境，名称通常是用户账号、用户名之类的内容，通过命名空间+名称+三列算法算出`UUID` 。
4. Version 4 （基于随机数）: `UUID-v4` 组成 `UUID v4` 的位是随机生成的，没有固有逻辑（除了第三段首个数字，该数字标识版本号），不包含命名空间、设备信息、时间信息。 故，`UUID-v4` 最容易理解、应用也最为广泛。
5. Version 5 （基于使用 SHA-1 的命名）： `UUID-v5` 和`V3` 类似，区别在于散列算法，使用了`sha1` 散列算法。

因为当前 MD5 的散列算法已经不推荐大规模使用了，所以如果你还需要使用 UUID V3 的话，会推荐使用 V5。

SHA-1 散列算法是当前被建议替代 MD5 使用的算法。

## _UUID_ 类

Java 已经在自己的 JDK 中集成了 UUID 的算法，你可以直接使用 UUID 的构造函数来生成一个随机的 UUID。

UUID 有一个单独的构造函数来为你创建 UUID。

```auto
UUID uuid = new UUID(long mostSignificant64Bits, long leastSignificant64Bits);

```

上面的构造函数需要你提供 2 个 _long_ 类型的参数，在实际使用的时候这个就比较麻烦了。

为了简化使用，UUID 还提供了 3 个静态方法来让你更快的创建 UUID 对象。

使用下面的方法来创建一个 V3 版本的 UUID，这个时候你需要提供一个 buytes 的数组。

```auto
UUID uuid = UUID.nameUUIDFromBytes(byte[] bytes);

```

下面这个方法是我们用得最多的，大部分情况下我们都会使用下面的这个方法，这个方法将会生成一个 V4 的 UUID，你不需要为这个方法提供任何参数：

```auto
UUID uuid = UUID.randomUUID();

```

还有一个静态方法就是通过提供一个 UUID 字符串来生成一个新的 UUID。

```auto
UUID uuid = UUID.fromString(String uuidHexDigitString);

```

下面来让我们看下，如何不使用 JDK 中给定的 UUID 方法来实现我们需要的 UUID。

## 实现

针对 UUID 的实现，我们分成 2 类。

针对只需要 UUID 的情况，我们实现使用 _UUIDv1_ 和 _UUIDv4_ 通常是最优的选择。

如果你的计算机是基于相同名字的，那么你应该选择使用 _UUIDv3_ 或者 _UUIDv5_。

因为，RFC 4122 的标准中没有针对 UUIDv2 进行说明，所以我们就不在这里对实现进行讨论的。

同时也是因为 UUIDv3 有很大的局限性，在实际使用情况下并没有被大规模推广，所以目前属于基本忽略不计的状态。

### _UUIDv1_ 和 _UUIDv4_

针对 _UUIDv1_ 这个版本，如果你担心你的 MAC 地址被泄露的话，你可以使用一个随机 48-bit 数组来替换 MAC 地址。

首先，我们先生成第一个 64 的值，并返回 _long_ ：

```auto
    private static long get64LeastSignificantBitsForVersion1() {
        final long random63BitLong = new Random().nextLong() & 0x3FFFFFFFFFFFFFFFL;
        long variant3BitFlag = 0x8000000000000000L;
        return random63BitLong | variant3BitFlag;
    }

    private static long get64MostSignificantBitsForVersion1() {
        final long currentTimeMillis = System.currentTimeMillis();
        final long time_low = (currentTimeMillis & 0x0000_0000_FFFF_FFFFL) << 32;
        final long time_mid = ((currentTimeMillis >> 32) & 0xFFFF) << 16;
        final long version = 1 << 12;
        final long time_high = ((currentTimeMillis >> 48) & 0x0FFF);
        return time_low | time_mid | version | time_high;
    }

```

在生成上面 2 个值之后，我们在使用的 JDK 的 UUID 算法来生成 generateType1UUID。

```auto
public static UUID generateType1UUID() {

    long most64SigBits = get64MostSignificantBitsForVersion1();
    long least64SigBits = get64LeastSignificantBitsForVersion1();

    return new UUID(most64SigBits, least64SigBits);
}

```

下面我们来看看 UUIDv4 的生成算法，这个算法是我们目前最常用的算法了。

在这个算法中，JDK 实现了 _SecureRandom_ 这个随机数生成器，通过这个函数通常用来降低可能出现重复的可能性。

```auto
UUID uuid = UUID.randomUUID();

```

上面的算法就是 JDK 中对 UUIDv4 实现的方法。

因为简单，所以被大规模使用。

### _UUIDv3_ 和 _UUIDv5_

这个 UUID 的生成使用的使用命名空间和名字的散列算法。

命名空间使用类似下面的定义 UUIDs like Domain Name System (DNS), Object Identifiers (OIDs), 和URLs。

让我们来看下这个算法的伪代码：

```auto
UUID = hash(NAMESPACE_IDENTIFIER + NAME)

```

_UUIDv3_ 和 _UUIDv5_ 的不同在于使用的散列算法的不同，_UUIDv3_ 使用的是 MD5 (128 bits) 的散列算法， _UUIDv5_ 使用的是 SHA-1 (160 bits) 的散列算法。

基于上面 2 个版本的实现我们就不再在这里具体讨论了，因为用得不多，可以在有时间的时候做下研究，在实际开发的时候可能不会被采用。

## 结论和总结

在本文章中，我们对 Java 实现的 UUID 算法进行了一些说明。

需要注意的时候，我们大部分情况使用的 UUID 版本是 UUIDv4，主要原因还是因为简单，适合使用。

针对一个相对独立的命名空间，可能会被要求使用 _UUIDv3_ 或 _UUIDv5_，如果被要求必须使用 _UUIDv3_ 或 _UUIDv5_ 的话，请使用 _UUIDv5_。
