1. 概要

UUID (Universally Unique Identifier)は、GUID(Globally Unique Identifier)とも呼ばれ、すべての実用的な目的で一意の128ビット値です。 それらの一意性は、他のほとんどの番号付けスキームとは異なり、中央の登録機関またはそれらを生成する当事者間の調整に依存しません。

このチュートリアルでは、JavaでUUID識別子を生成するための2つの異なる実装アプローチを紹介します。

2. 構造

UUIDの例を見てから、UUIDの正規表現を見てみましょう。

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

標準表現は、32個の16進数(基数16)で構成され、ハイフンで区切られた5つのグループに8-4-4-4-12の形式で表示され、合計36文字(32個の16進文字と4個のハイフン)になります。 。

Nil UUIDは、すべてのビットがゼロであるUUIDの特殊な形式です。

2.1. バリアント

上記の標準表現では、AはUUIDバリアントを示します。これはUUIDのレイアウトを決定します。 UUIDの他のすべてのビットは、バリアントフィールドのビットの設定によって異なります。

バリアントは、Aの最上位3ビットによって決定されます。

  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のAの値は「a」です。 「a」(= 10xx)に相当するバイナリは、バリアントを2として示します。

2.1. バージョン

標準表現をもう一度見てみると、Bはバージョンを表しています。 バージョンフィールドは、指定されたUUIDのタイプを説明する値を保持します。 上記のUUIDの例のバージョン( B の値)は4です。

5つの異なる基本タイプのUUIDがあります。

  1. バージョン1(時間ベース):現在のタイムスタンプに基づいており、1582年10月15日から100ナノ秒単位で測定され、UUIDが作成されたデバイスのMACアドレスと連結されます。
  2. バージョン2(DCE –分散コンピューティング環境):ローカルマシンのネットワークインターフェイスのMACアドレス(またはノード)とともに、現在の時刻を使用します。 さらに、バージョン2のUUIDは、時間フィールドの下位部分を、UUIDを作成したローカルアカウントのユーザーIDやグループIDなどのローカル識別子に置き換えます。
  3. バージョン3(名前ベース):UUIDは、名前空間と名前のハッシュを使用して生成されます。 名前空間識別子は、ドメインネームシステム(DNS)、オブジェクト識別子(OID)、URLなどのUUIDです。
  4. バージョン4(ランダムに生成):このバージョンでは、UUID識別子はランダムに生成され、作成された時刻や生成されたマシンに関する情報は含まれていません。
  5. バージョン5(SHA-1を使用した名前ベース):バージョン3と同じアプローチを使用して生成されますが、ハッシュアルゴリズムが異なります。 このバージョンでは、名前空間の識別子と名前のSHA-1(160ビット)ハッシュを使用します。

3. UUIDクラス

Javaには、UUIDをランダムに生成する場合でも、コンストラクターを使用して作成する場合でも、UUID識別子を管理するための実装が組み込まれています。

UUID クラスには単一のコンストラクターがあります:

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

このコンストラクターを使用する場合は、2つのlong値を指定する必要があります。 ただし、UUIDのビットパターンを自分で作成する必要があります。

便宜上、UUIDを作成するための3つの静的メソッドがあります。

最初のメソッドは、指定されたバイト配列からバージョン3のUUIDを作成します。

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

次に、 randomUUID()メソッドはバージョン4のUUIDを作成します。 これは、UUIDインスタンスを作成する最も便利な方法です。

UUID uuid = UUID.randomUUID();

3番目の静的メソッドは、指定されたUUIDの文字列表現を指定すると、UUIDオブジェクトを返します。

UUID uuid = UUID.fromString(String uuidHexDigitString);

次に、組み込みのUUIDクラスを使用せずにUUIDを生成するためのいくつかの実装を見てみましょう。

4. 実装

要件に応じて、実装を2つのカテゴリに分類します。 最初のカテゴリは、一意である必要があるだけの識別子用であり、そのためには、UUIDv1およびUUIDv4が最適なオプションです。 2番目のカテゴリでは、指定された名前から常に同じUUIDを生成する必要がある場合、UUIDv3またはUUIDv5が必要になります。

RFC 4122は正確な生成の詳細を指定していないため、この記事ではUUIDv2の実装については説明しません。

ここで、前述のカテゴリの実装を見てみましょう。

4.1. バージョン1および4

まず、プライバシーが懸念される場合は、 UUIDv1 を、MACアドレスの代わりにランダムな48ビット数で生成することもできます。 この記事では、この代替案を見ていきます。

まず、64個の最下位ビットと最上位ビットをlong値として生成します。

private static long get64LeastSignificantBitsForVersion1() {
    Random random = new Random();
    long random63BitLong = random.nextLong() & 0x3FFFFFFFFFFFFFFFL;
    long variant3BitFlag = 0x8000000000000000L;
    return random63BitLong + variant3BitFlag;
}

private static long get64MostSignificantBitsForVersion1() {
    LocalDateTime start = LocalDateTime.of(1582, 10, 15, 0, 0, 0);
    Duration duration = Duration.between(start, LocalDateTime.now());
    long seconds = duration.getSeconds();
    long nanos = duration.getNano();
    long timeForUuidIn100Nanos = seconds * 10000000 + nanos * 100;
    long least12SignificatBitOfTime = (timeForUuidIn100Nanos & 0x000000000000FFFFL) >> 4;
    long version = 1 << 12;
    return 
      (timeForUuidIn100Nanos & 0xFFFFFFFFFFFF0000L) + version + least12SignificatBitOfTime;
}

次に、これら2つの値をUUIDのコンストラクターに渡すことができます。

public static UUID generateType1UUID() {

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

    return new UUID(most64SigBits, least64SigBits);
}

UUIDv4を生成する方法を見ていきます。 実装では、ソースとしてランダムな数値を使用します。 Javaの実装はSecureRandomであり、衝突の可能性を減らすために、予測できない値をシードとして使用してランダムな数値を生成します。

バージョン4UUIDを生成してみましょう。

UUID uuid = UUID.randomUUID();

次に、「SHA-256」とランダムなUUIDを使用して一意のキーを生成しましょう。

MessageDigest salt = MessageDigest.getInstance("SHA-256");
salt.update(UUID.randomUUID().toString().getBytes("UTF-8"));
String digest = bytesToHex(salt.digest());

4.2. バージョン3および5

UUIDは、名前空間と名前のハッシュを使用して生成されます。 名前空間識別子は、ドメインネームシステム(DNS)、オブジェクト識別子(OID)、URLなどのUUIDです。 アルゴリズムの擬似コードを見てみましょう。

UUID = hash(NAMESPACE_IDENTIFIER + NAME)

UUIDv3UUIDv5の唯一の違いは、ハッシュアルゴリズムです。v3はMD5(128ビット)を使用し、v5はSHA-1(160ビット)を使用します。

UUIDv3 には、 UUID クラスのメソッドnameUUIDFromBytes()を使用します。このメソッドは、バイトの配列を取得してMD5ハッシュを適用します。

それでは、最初に名前空間と特定の名前からバイト表現を抽出し、それらを単一の配列に結合してUUIDAPIに送信しましょう。

byte[] nameSpaceBytes = bytesFromUUID(namespace);
byte[] nameBytes = name.getBytes("UTF-8");
byte[] result = joinBytes(nameSpaceBytes, nameBytes);

最後のステップは、前のプロセスから取得した結果を nameUUIDFromBytes()メソッドに渡すことです。 このメソッドは、バリアントフィールドとバージョンフィールドも設定します。

UUID uuid = UUID.nameUUIDFromBytes(result);

UUIDv5の実装を見てみましょう。 Javaには、バージョン5を生成するための組み込みの実装が用意されていないことに注意してください。

コードをチェックして、 long 値として、最下位ビットと最上位ビットを生成してみましょう。

public static long getLeastAndMostSignificantBitsVersion5(final byte[] src, final int offset, final ByteOrder order) {
    long ans = 0;
    if (order == ByteOrder.BIG_ENDIAN) {
        for (int i = offset; i < offset + 8; i += 1) {
            ans <<= 8;
            ans |= src[i] & 0xffL;
        }
    } else {
        for (int i = offset + 7; i >= offset; i -= 1) {
            ans <<= 8;
            ans |= src[i] & 0xffL;
        }
    }
    return ans;
}

次に、UUIDを生成するために名前を付けるメソッドを定義する必要があります。 このメソッドは、UUIDクラスで定義されたデフォルトのコンストラクターを使用します。

private static UUID generateType5UUID(String name) { 
    byte[] bytes = name.getBytes(StandardCharsets.UTF_8);
    MessageDigest md = MessageDigest.getInstance("SHA-1");
    byte[] hash = md.digest(bytes);
    long msb = getLeastAndMostSignificantBitsVersion5(hash, 0, ByteOrder.BIG_ENDIAN);
    long lsb = getLeastAndMostSignificantBitsVersion5(hash, 8, ByteOrder.BIG_ENDIAN);
    msb &= ~(0xfL << 12);
    msb |= ((long) 5) << 12;
    lsb &= ~(0x3L << 62);
    lsb |= 2L << 62;
    return new UUID(msb, lsb);
}

5. 結論

この記事では、UUID識別子に関する主な概念と、組み込みクラスを使用してそれらを生成する方法について説明しました。 次に、さまざまなバージョンのUUIDとそのアプリケーションスコープの効率的な実装をいくつか確認しました。

いつものように、この記事の完全なコードは、GitHubから入手できます。