1. 개요

GUID(Globally Unique Identifier)라고도 하는 UUID (Universally Unique Identifier)는 실용적인 용도로 고유한 128비트 값입니다. 고유성은 대부분의 다른 번호 매기기 체계와 달리 중앙 등록 기관이나 이를 생성하는 당사자 간의 조정에 의존하지 않습니다 .

이 사용방법(예제)에서는 Java에서 UUID 식별자를 생성하는 두 가지 구현 방법을 살펴봅니다.

2. 구조

UUID의 표준 표현이 뒤따르는 예제 UUID를 살펴보겠습니다.

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

표준 표현은 32개의 16진수(base-16) 숫자로 구성되며 하이픈으로 구분된 5개의 그룹으로 표시되며 8-4-4-4-12 형식으로 총 36자(16진수 32개 및 하이픈 4개)로 표시됩니다. .

Nil UUID는 모든 비트가 0인 특수 형태의 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입니다.

UUID 에는 5가지 기본 유형이 있습니다 .

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

이 생성자를 사용하려면 두 개의 값을 제공해야 합니다. 그러나 UUID에 대한 비트 패턴을 직접 구성해야 합니다.

편의를 위해 UUID 를 생성하는 세 가지 정적 메서드가 있습니다 .

첫 번째 방법은 주어진 바이트 배열에서 버전 3 UUID를 생성합니다.

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

둘째, randomUUID() 메서드는 버전 4 UUID를 생성합니다. 이것은 UUID 인스턴스를 생성하는 가장 편리한 방법입니다.

UUID uuid = UUID.randomUUID();

세 번째 정적 메서드는 주어진 UUID의 문자열 표현이 주어진 UUID 객체를 반환합니다.

UUID uuid = UUID.fromString(String uuidHexDigitString);

이제 내장 UUID 클래스를 사용하지 않고 UUID를 생성하기 위한 몇 가지 구현을 살펴보겠습니다.

4. 구현

요구 사항에 따라 구현을 두 가지 범주로 구분할 것입니다. 첫 번째 범주는 고유해야 하는 식별자에 대한 것이며 이를 위해 UUIDv1UUIDv4 가 최상의 옵션입니다. 두 번째 범주에서 주어진 이름에서 항상 동일한 UUID를 생성해야 하는 경우 UUIDv3 또는 UUIDv5 가 필요합니다 .

RFC 4122는 정확한 생성 세부 정보를 지정하지 않으므로 이 기사에서는 UUIDv2 구현을 살펴보지 않습니다.

이제 우리가 언급한 범주에 대한 구현을 살펴보겠습니다.

4.1. 버전 1 및 4

우선 프라이버시가 우려 되는 경우 MAC 주소 대신 랜덤의 48비트 숫자로 UUIDv1 을 생성할 수 있습니다. 이 기사에서는 이 대안을 살펴보겠습니다.

먼저 64개의 최소 및 최상위 비트를 long 값으로 생성합니다.

private static long get64LeastSignificantBitsForVersion1() {
    long random63BitLong = new 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 least12SignificantBitOfTime = (timeForUuidIn100Nanos & 0x000000000000FFFFL) >> 4;
    long version = 1 << 12;
    return (timeForUuidIn100Nanos & 0xFFFFFFFFFFFF0000L) + version + least12SignificatBitOfTime;
}

그런 다음 이 두 값을 UUID 의 생성자에 전달할 수 있습니다 .

public static UUID generateType1UUID() {

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

    return new UUID(most64SigBits, least64SigBits);
}

이제 UUIDv4를 생성하는 방법을 살펴보겠습니다. 구현은 난수를 소스로 사용합니다. Java 구현은 충돌 가능성을 줄이기 위해 예측할 수 없는 값을 시드로 사용하여 난수를 생성하는 SecureRandom 입니다.

버전 4 UUID 를 생성해 보겠습니다 .

UUID uuid = UUID.randomUUID();

그런 다음 "SHA-256"과 랜덤의 UUID 를 사용하여 고유 키를 생성해 보겠습니다 .

MessageDigest salt = MessageDigest.getInstance("SHA-256");
salt.update(UUID.randomUUID().toString().getBytes(StandardCharsets.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 의 경우 바이트 배열을 사용하고 MD5 해시를 적용하는 UUID 클래스 nameUUIDFromBytes (String namespace, String name) 메서드를 사용합니다 .

따라서 먼저 네임스페이스와 특정 이름에서 바이트 표현을 추출하고 단일 배열로 결합하여 UUID api로 보냅니다.

byte[] nameSpaceBytes = bytesFromUUID(namespace);
byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8);
byte[] result = joinBytes(nameSpaceBytes, nameBytes);

마지막 단계는 이전 프로세스에서 얻은 결과를 nameUUIDFromBytes() 메서드에 전달하는 것입니다. 이 메서드는 변형 및 버전 필드도 설정합니다.

UUID uuid = UUID.nameUUIDFromBytes(result);

이제 UUIDv5 구현을 살펴보겠습니다 . Java는 버전 5를 생성하기 위한 내장 구현을 제공하지 않는다는 점에 유의해야 합니다.

다시 긴으로 최하위 비트와 최상위 비트를 생성하는 코드를 확인해 보겠습니다 .

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

이제 이름을 사용하여 UUID를 생성하는 메서드를 정의해야 합니다. 이 메서드는 UUID 클래스에 정의된 기본 생성자를 사용합니다.

public static UUID generateType5UUID(String name) {

    try {

        byte[] bytes = name.getBytes(StandardCharsets.UTF_8);
        MessageDigest md = MessageDigest.getInstance("SHA-1");

        byte[] hash = md.digest(bytes);

        long msb = getLeastAndMostSignificantBitsVersion5(hash, 0);
        long lsb = getLeastAndMostSignificantBitsVersion5(hash, 8);
         // Set the version field
        msb &= ~(0xfL << 12);
        msb |= 5L << 12;
        // Set the variant field to 2
        lsb &= ~(0x3L << 62);
        lsb |= 2L << 62;
        return new UUID(msb, lsb);

    } catch (NoSuchAlgorithmException e) {
        throw new AssertionError(e);
    }
}

5. 결론

이 기사에서는 UUID 식별자에 대한 주요 개념과 내장 클래스를 사용하여 식별자를 생성하는 방법을 살펴보았습니다. 그런 다음 다양한 버전의 UUID와 해당 응용 프로그램 범위에 대한 몇 가지 효율적인 구현을 확인했습니다.

항상 그렇듯이 이 기사의 전체 코드는 GitHub에서 사용할 수 있습니다 .

Generic footer banner