Перейти к основному содержимому

Создание буквенно-цифровой строки UUID в Java

· 7 мин. чтения

1. Обзор

UUID (универсальный уникальный идентификатор), также известный как GUID (глобальный уникальный идентификатор), представляет собой 128-битное значение, уникальное для всех практических целей. Их уникальность не зависит от центрального регистрационного органа или координации между генерирующими их сторонами , в отличие от большинства других схем нумерации.

В этом руководстве мы увидим два разных подхода к реализации для создания идентификаторов UUID в Java.

2. Структура

Давайте посмотрим на пример UUID, за которым следует каноническое представление UUID:

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

Стандартное представление состоит из 32 шестнадцатеричных (с основанием 16) цифр, отображаемых в пяти группах, разделенных дефисами, в форме 8-4-4-4-12, всего 36 символов (32 шестнадцатеричных символа и 4 дефиса). .

Nil UUID — это особая форма UUID, в которой все биты равны нулю.

2.1. Варианты

В приведенном выше стандартном представлении A указывает вариант UUID , который определяет макет UUID. Все остальные биты в UUID зависят от установки битов в поле варианта.

Вариант определяется тремя старшими битами A :

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)

Значение A в упомянутом UUID равно «a». Двоичный эквивалент «a» (=10xx) показывает вариант как 2.

2.1. Версии

Снова взглянув на стандартное представление, B представляет версию . Поле версии содержит значение, описывающее тип данного UUID . Версия (значение B ) в приведенном выше примере UUID равна 4.

Существует пять различных основных типов UUID :

  1. Версия 1 (на основе времени): на основе текущей метки времени, измеренной в единицах 100 наносекунд от 15 октября 1582 года, объединенной с MAC-адресом устройства, на котором создан UUID.
  2. Версия 2 (DCE — Distributed Computing Environment): использует текущее время вместе с MAC-адресом (или узлом) для сетевого интерфейса на локальном компьютере. Кроме того, UUID версии 2 заменяет нижнюю часть поля времени локальным идентификатором, таким как идентификатор пользователя или идентификатор группы локальной учетной записи, которая создала UUID.
  3. Версия 3 (на основе имени): UUID генерируются с использованием хэша пространства имен и имени. Идентификаторы пространства имен — это UUID, такие как система доменных имен (DNS), идентификаторы объектов (OID) и URL-адреса.
  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 .

Первый метод создает UUID версии 3 из заданного массива байтов:

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

Во-вторых, метод randomUUID() создает UUID версии 4. Это наиболее удобный способ создания экземпляра UUID :

UUID uuid = UUID.randomUUID();

Третий статический метод возвращает объект UUID с учетом строкового представления данного UUID:

UUID uuid = UUID.fromString(String uuidHexDigitString);

Давайте теперь рассмотрим некоторые реализации для генерации UUID без использования встроенного класса UUID .

4. Реализации

Мы собираемся разделить реализации на две категории в зависимости от требований. Первая категория предназначена для идентификаторов, которые должны быть уникальными, и для этой цели лучше всего подходят UUIDv1 и UUIDv4 . Во второй категории, если нам нужно всегда генерировать один и тот же UUID из заданного имени, нам понадобится UUIDv3 или UUIDv5 .

Поскольку в RFC 4122 не указаны точные детали генерации, в этой статье мы не будем рассматривать реализацию UUIDv2 .

Давайте теперь посмотрим на реализацию для категорий, которые мы упомянули.

4.1. Версии 1 и 4

Прежде всего, если конфиденциальность является проблемой, UUIDv1 может быть сгенерирован случайным 48-битным числом вместо MAC-адреса. В этой статье мы рассмотрим эту альтернативу.

Во-первых, мы сгенерируем 64 младших и старших бита в виде длинных значений:

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

Затем мы можем передать эти два значения конструктору UUID :

public static UUID generateType1UUID() {

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

return new UUID(most64SigBits, least64SigBits);
}

Теперь мы посмотрим, как сгенерировать UUIDv4. Реализация использует случайные числа в качестве источника. Реализация Java — SecureRandom , которая использует непредсказуемое значение в качестве начального значения для генерации случайных чисел, чтобы уменьшить вероятность коллизий.

Давайте сгенерируем UUID версии 4 :

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 генерируются с использованием хэша пространства имен и имени. Идентификаторы пространства имен — это UUID, такие как система доменных имен (DNS), идентификаторы объектов (OID) и URL-адреса. Давайте посмотрим на псевдокод алгоритма:

UUID = hash(NAMESPACE_IDENTIFIER + NAME)

Единственная разница между UUIDv3 и UUIDv5 заключается в алгоритме хеширования — v3 использует MD5 (128 бит), а v5 использует SHA-1 (160 бит).

Для UUIDv3 мы будем использовать метод nameUUIDFromBytes() из класса UUID , который принимает массив байтов и применяет хэш MD5.

Итак, давайте сначала извлечем представление байтов из пространства имен и конкретного имени и объединим их в один массив, чтобы отправить его в API UUID:

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

Последним шагом будет передача результата, полученного в предыдущем процессе, методу nameUUIDFromBytes() . Этот метод также установит поля варианта и версии:

UUID uuid = UUID.nameUUIDFromBytes(result);

Давайте теперь посмотрим на реализацию для UUIDv5 . Важно отметить, что Java не предоставляет встроенной реализации для создания версии 5.

Давайте проверим код, чтобы сгенерировать младшие и самые значащие биты, снова как длинные значения:

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 .