Создать GUID / UUID в JavaScript? - PullRequest
       577

Создать GUID / UUID в JavaScript?

3682 голосов
/ 20 сентября 2008

Я пытаюсь создать глобально уникальные идентификаторы в JavaScript. Я не уверен, какие подпрограммы доступны во всех браузерах, насколько «случайным» и затравленным является встроенный генератор случайных чисел и т. Д.

Значение GUID / UUID должно быть не менее 32 символов и должно оставаться в диапазоне ASCII, чтобы избежать проблем при их передаче.

Ответы [ 53 ]

3531 голосов
/ 22 января 2010

Для решения, совместимого с RFC4122 версии 4, это однострочное (ish) решение является наиболее компактным из всех, что я мог придумать.

function uuidv4() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
}

console.log(uuidv4())

Обновление, 2015-06-02 : имейте в виду, что уникальность UUID в значительной степени зависит от базового генератора случайных чисел (RNG). В приведенном выше решении для краткости используется Math.random(), однако Math.random() означает , а не , гарантированно являющийся высококачественным ГСЧ. См. Адама Хайленда в превосходной рецензии на Math.random () для подробностей. Для более надежного решения рассмотрим что-то вроде модуль uuid [Отказ от ответственности: я автор], который использует API RNG более высокого качества, где это возможно.

Обновление, 2015-08-26 : В качестве дополнительного примечания в этом gist описывается, как определить, сколько идентификаторов может быть сгенерировано до достижения определенной вероятности столкновения. Например, с 3,26x10 15 версии 4 UCID RFC4122 у вас есть вероятность столкновения 1 на миллион.

Обновление, 2017-06-28 : хорошая статья от разработчиков Chrome , в которой обсуждается состояние качества Math.random PRNG в Chrome, Firefox и Safari. tl; dr - По состоянию на конец 2015 года это "довольно хорошо", но не криптографическое качество. Чтобы решить эту проблему, вот обновленная версия вышеупомянутого решения, которая использует ES6, crypto API и немного волшебства JS. Я не могу взять кредит на :

function uuidv4() {
  return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
    (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  )
}

console.log(uuidv4());
2148 голосов
/ 20 сентября 2008

UUID (универсальный уникальный идентификатор), также известный как GUID (глобальный уникальный идентификатор), согласно RFC 4122 , являются идентификаторами с определенной гарантией уникальности.

Лучший способ их создания - следовать инструкциям по реализации в указанном RFC, использовать одну из многих проверенных реализаций с открытым исходным кодом.

Популярный инструмент с открытым исходным кодом для работы с UUID в JavaScript: node-uuid

Обратите внимание, что просто случайная генерация идентификаторов побайтово или посимвольно не даст вам таких же гарантий, как соответствующая реализация. Кроме того, очень важно, что системы, работающие с совместимыми UUID, могут не принимать случайно сгенерированные, и многие валидаторы с открытым исходным кодом фактически проверяют правильность структуры.

UUID должен иметь этот формат:

xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

Где позиции M и N могут иметь только определенные значения. В настоящее время единственными допустимыми значениями для M являются 1, 2, 3, 4 и 5, поэтому случайная генерация этой позиции сделает большинство результатов неприемлемыми.

741 голосов
/ 10 января 2012

Мне действительно нравится, насколько чистым является ответ Брофы , но, к сожалению, плохие реализации Math.random оставляют возможность для столкновения.

Вот аналогичное RFC4122 версия 4-совместимое решение, которое решает эту проблему путем смещения первых 13 шестнадцатеричных чисел шестнадцатеричной частью временной метки. Таким образом, даже если Math.random находится на одном и том же начальном этапе, оба клиента должны будут сгенерировать UUID в одну и ту же миллисекунду (или более 10 000 лет спустя), чтобы получить тот же UUID:

function generateUUID() { // Public Domain/MIT
    var d = new Date().getTime();
    if (typeof performance !== 'undefined' && typeof performance.now === 'function'){
        d += performance.now(); //use high-precision timer if available
    }
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = (d + Math.random() * 16) % 16 | 0;
        d = Math.floor(d / 16);
        return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });
}


Вот скрипка для проверки.

367 голосов
/ 23 февраля 2014
Ответ

брофы довольно приятный, действительно - впечатляюще умный, действительно ... rfc4122-совместимый, несколько читабельный и компактный. Отлично!

Но если вы посмотрите на это регулярное выражение, эти многочисленные replace() обратные вызовы, toString() и Math.random() вызовы функций (где он использует только 4 бита результата и тратит впустую остальное), вы можете начать задумываться о производительности. Действительно, joelpt даже решил отказаться от RFC для общей скорости GUID с generateQuickGUID.

Но можем ли мы получить соответствие скорости и RFC? Я говорю ДА! Можем ли мы сохранить читабельность? Ну ... Не совсем, но легко, если вы будете следовать.

Но сначала, мои результаты, по сравнению с бройфой, guid (принятый ответ) и несоответствием rfc generateQuickGuid:

                  Desktop   Android
           broofa: 1617ms   12869ms
               e1:  636ms    5778ms
               e2:  606ms    4754ms
               e3:  364ms    3003ms
               e4:  329ms    2015ms
               e5:  147ms    1156ms
               e6:  146ms    1035ms
               e7:  105ms     726ms
             guid:  962ms   10762ms
generateQuickGuid:  292ms    2961ms
  - Note: 500k iterations, results will vary by browser/cpu.

Таким образом, благодаря шестой итерации оптимизаций я побил самый популярный ответ более чем на 12X , принятый ответ - на 9X , а быстрый несовместимый ответ - на 2-3x . И я все еще совместим с rfc4122.

Заинтересованы в том, как? Я поставил полный исходный код на http://jsfiddle.net/jcward/7hyaC/3/ и на http://jsperf.com/uuid-generator-opt/4

Для пояснения давайте начнем с кода брофы:

'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
  return v.toString(16);
});

Таким образом, он заменяет x на любую случайную шестнадцатеричную цифру, y - на случайные данные (за исключением того, что старшие 2 бита равны 10 согласно спецификации RFC), и регулярное выражение не соответствует - или 4 символов, поэтому он не должен иметь с ними дело. Очень, очень гладко.

Первое, что нужно знать, это то, что вызовы функций дорогие, как и регулярные выражения (хотя он использует только 1, он имеет 32 обратных вызова, по одному на каждое совпадение, и в каждом из 32 обратных вызовов он вызывает Math.random () и v.toString (16)).

Первый шаг к повышению производительности - исключить RegEx и его функции обратного вызова и использовать вместо этого простой цикл. Это означает, что мы должны иметь дело с символами - и 4, тогда как брофа этого не делает. Также обратите внимание, что мы можем использовать индексирование String Array, чтобы сохранить его архитектуру шаблона String:

function e1() {
  var u='',i=0;
  while(i++<36) {
    var c='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'[i-1],r=Math.random()*16|0,v=c=='x'?r:(r&0x3|0x8);
    u+=(c=='-'||c=='4')?c:v.toString(16)
  }
  return u;
}

По сути, та же самая внутренняя логика, за исключением того, что мы проверяем - или 4, и использование цикла while (вместо replace() обратных вызовов) дает нам почти трехкратное улучшение!

Следующим шагом является небольшой шаг на рабочем столе, но он имеет существенное значение для мобильных устройств. Давайте сделаем меньше вызовов Math.random () и используем все эти случайные биты вместо того, чтобы выбрасывать 87% из них со случайным буфером, который смещается при каждой итерации. Давайте также уберем это определение шаблона из цикла, на случай, если это поможет:

function e2() {
  var u='',m='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx',i=0,rb=Math.random()*0xffffffff|0;
  while(i++<36) {
    var c=m[i-1],r=rb&0xf,v=c=='x'?r:(r&0x3|0x8);
    u+=(c=='-'||c=='4')?c:v.toString(16);rb=i%8==0?Math.random()*0xffffffff|0:rb>>4
  }
  return u
}

Это экономит нам 10-30% в зависимости от платформы. Неплохо. Но следующий большой шаг избавляет от вызовов функции toString вместе с классикой оптимизации - справочной таблицей. Простая 16-элементная таблица поиска будет выполнять работу toString (16) за гораздо меньшее время:

function e3() {
  var h='0123456789abcdef';
  var k='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
  /* same as e4() below */
}
function e4() {
  var h=['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'];
  var k=['x','x','x','x','x','x','x','x','-','x','x','x','x','-','4','x','x','x','-','y','x','x','x','-','x','x','x','x','x','x','x','x','x','x','x','x'];
  var u='',i=0,rb=Math.random()*0xffffffff|0;
  while(i++<36) {
    var c=k[i-1],r=rb&0xf,v=c=='x'?r:(r&0x3|0x8);
    u+=(c=='-'||c=='4')?c:h[v];rb=i%8==0?Math.random()*0xffffffff|0:rb>>4
  }
  return u
}

Следующая оптимизация - еще одна классика. Поскольку в каждой итерации цикла мы обрабатываем только 4-битные выходные данные, давайте сократим количество циклов пополам и обработаем 8-битные в каждой итерации. Это сложно, поскольку нам все еще приходится обрабатывать битовые позиции, соответствующие RFC, но это не так уж сложно. Затем нам нужно создать таблицу просмотра большего размера (16x16 или 256) для хранения 0x00 - 0xff, и мы создадим ее только один раз, вне функции e5 ().

var lut = []; for (var i=0; i<256; i++) { lut[i] = (i<16?'0':'')+(i).toString(16); }
function e5() {
  var k=['x','x','x','x','-','x','x','-','4','x','-','y','x','-','x','x','x','x','x','x'];
  var u='',i=0,rb=Math.random()*0xffffffff|0;
  while(i++<20) {
    var c=k[i-1],r=rb&0xff,v=c=='x'?r:(c=='y'?(r&0x3f|0x80):(r&0xf|0x40));
    u+=(c=='-')?c:lut[v];rb=i%4==0?Math.random()*0xffffffff|0:rb>>8
  }
  return u
}

Я попробовал e6 (), который обрабатывает 16 бит одновременно, все еще используя LUT из 256 элементов, и он показал убывающую отдачу от оптимизации. Несмотря на меньшее количество итераций, внутренняя логика усложнялась из-за увеличения обработки, и она выполняла то же самое на настольном компьютере и только на ~ 10% быстрее на мобильном.

Последняя применяемая техника оптимизации - разверните цикл. Поскольку мы зацикливаемся фиксированное количество раз, технически мы можем выписать все это вручную. Я попробовал это однажды с одной случайной переменной r, которую я продолжал переназначать, и производительность снижалась. Но с четырьмя переменными, которым заранее назначены случайные данные, затем с помощью таблицы поиска и применения соответствующих битов RFC, эта версия выкуривает их все:

var lut = []; for (var i=0; i<256; i++) { lut[i] = (i<16?'0':'')+(i).toString(16); }
function e7()
{
  var d0 = Math.random()*0xffffffff|0;
  var d1 = Math.random()*0xffffffff|0;
  var d2 = Math.random()*0xffffffff|0;
  var d3 = Math.random()*0xffffffff|0;
  return lut[d0&0xff]+lut[d0>>8&0xff]+lut[d0>>16&0xff]+lut[d0>>24&0xff]+'-'+
    lut[d1&0xff]+lut[d1>>8&0xff]+'-'+lut[d1>>16&0x0f|0x40]+lut[d1>>24&0xff]+'-'+
    lut[d2&0x3f|0x80]+lut[d2>>8&0xff]+'-'+lut[d2>>16&0xff]+lut[d2>>24&0xff]+
    lut[d3&0xff]+lut[d3>>8&0xff]+lut[d3>>16&0xff]+lut[d3>>24&0xff];
}

Модулированный: http://jcward.com/UUID.js - UUID.generate()

Самое смешное, генерировать 16 байтов случайных данных - это самая простая часть. Весь трюк состоит в том, чтобы выразить его в формате String с соблюдением требований RFC, и это наиболее точно достигается с помощью 16 байтов случайных данных, развернутого цикла и таблицы поиска.

Я надеюсь, что моя логика верна - очень легко ошибиться в этом утомительном занятии. Но результаты выглядят хорошо для меня. Надеюсь, вам понравилась эта безумная поездка благодаря оптимизации кода!

Имейте в виду: Моя основная цель состояла в том, чтобы показать и обучить потенциальным стратегиям оптимизации. Другие ответы охватывают важные темы, такие как столкновения и действительно случайные числа, которые важны для создания хороших UUID.

147 голосов
/ 17 мая 2009

Вот некоторый код, основанный на RFC 4122 , раздел 4.4 (Алгоритмы для создания UUID из действительно случайного или псевдослучайного числа).

function createUUID() {
    // http://www.ietf.org/rfc/rfc4122.txt
    var s = [];
    var hexDigits = "0123456789abcdef";
    for (var i = 0; i < 36; i++) {
        s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
    }
    s[14] = "4";  // bits 12-15 of the time_hi_and_version field to 0010
    s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);  // bits 6-7 of the clock_seq_hi_and_reserved to 01
    s[8] = s[13] = s[18] = s[23] = "-";

    var uuid = s.join("");
    return uuid;
}
107 голосов
/ 19 мая 2017
var uniqueId = Math.random().toString(36).substring(2) 
               + (new Date()).getTime().toString(36);

document.getElementById("unique").innerHTML =
  Math.random().toString(36).substring(2) + (new Date()).getTime().toString(36);
<div id="unique">
</div>

Если идентификаторы генерируются с интервалом более 1 миллисекунды, они уникальны на 100%.

Если два идентификатора генерируются с более короткими интервалами и при условии, что случайный метод действительно является случайным, это приведет к созданию идентификаторов, которые с вероятностью 99,9999999999999% будут глобально уникальными (столкновение в 1 из 10 ^ 15)

Вы можете увеличить это число, добавив больше цифр, но для создания 100% уникальных идентификаторов вам потребуется использовать глобальный счетчик.

если вам действительно нужно соответствие RFC, это форматирование будет считаться действительным GUID версии 4:

var u = (new Date()).getTime().toString(16) + 
    Math.random().toString(16).substring(2) + "0".repeat(16);
var guid = u.substr(0,8) + '-' + u.substr(8,4) + '-4000-8' + 
    u.substr(12,3) + '-' + u.substr(15,12);

var u = (new Date()).getTime().toString(16) + 
    Math.random().toString(16).substring(2) + "0".repeat(16);
var guid = u.substr(0,8) + '-' + u.substr(8,4) + '-4000-8' + 
    u.substr(12,3) + '-' + u.substr(15,12);
document.getElementById("unique").innerHTML = guid;
<div id="unique">
</div>

Редактировать: Приведенный выше код следует за намерением, но не буквой RFC. Среди других несоответствий это несколько случайных цифр. (Добавьте больше случайных цифр, если вам это нужно). Преимущество в том, что это действительно быстро, по сравнению со 100% совместимым кодом. Вы можете проверить свой GUID здесь

84 голосов
/ 22 мая 2013

Самый быстрый GUID, как метод строкового генератора в формате XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX. Это не создает стандартный GUID.

Десять миллионов выполнений этой реализации занимают всего 32,5 секунды, что является самым быстрым, что я когда-либо видел в браузере (единственное решение без циклов / итераций).

Функция проста:

/**
 * Generates a GUID string.
 * @returns {String} The generated GUID.
 * @example af8a8416-6e18-a307-bd9c-f2c947bbb3aa
 * @author Slavik Meltser (slavik@meltser.info).
 * @link http://slavik.meltser.info/?p=142
 */
function guid() {
    function _p8(s) {
        var p = (Math.random().toString(16)+"000000000").substr(2,8);
        return s ? "-" + p.substr(0,4) + "-" + p.substr(4,4) : p ;
    }
    return _p8() + _p8(true) + _p8(true) + _p8();
}

Чтобы проверить производительность, вы можете запустить этот код:

console.time('t'); 
for (var i = 0; i < 10000000; i++) { 
    guid(); 
};
console.timeEnd('t');

Я уверен, что большинство из вас поймет, что я там делал, но, возможно, есть хотя бы один человек, которому понадобится объяснение:

Алгоритм:

  • Функция Math.random() возвращает десятичное число от 0 до 1 с 16 цифрами после запятой (для пример 0.4363923368509859).
  • Тогда мы берем это число и конвертируем это в строку с основанием 16 (из примера выше мы получим 0.6fb7687f).
    Math.random().toString(16).
  • Затем мы обрезаем префикс 0. (0.6fb7687f => 6fb7687f) и получить строку с восемью шестнадцатеричными длинные символы.
    (Math.random().toString(16).substr(2,8).
  • Иногда функция Math.random() возвращает более короткое число (например, 0.4363) из-за нулей в конце (из приведенного выше примера на самом деле это число 0.4363000000000000). Вот почему я добавляю к этой строке "000000000" (строка с девятью нулями), а затем обрезаю ее с помощью функции substr(), чтобы сделать ее точно девятью символами (заполняя нули справа).
  • Причина добавления ровно девяти нулей кроется в худшем сценарии, когда функция Math.random() возвращает ровно 0 или 1 (вероятность 1/10 ^ 16 для каждого из них). Вот почему нам нужно было добавить к нему девять нулей ("0"+"000000000" или "1"+"000000000"), а затем отрезать его от второго индекса (3-й символ) длиной восемь символов. В остальных случаях добавление нулей не повредит результату, так как он все равно обрезает его.
    Math.random().toString(16)+"000000000").substr(2,8).

Сборка:

  • Идентификатор GUID имеет следующий формат XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.
  • Я разделил GUID на 4 части, каждая часть разделена на 2 типа (или формата): XXXXXXXX и -XXXX-XXXX.
  • Теперь я собираю GUID, используя эти 2 типа для сборки GUID с помощью вызова из 4 частей, следующим образом: XXXXXXXX -XXXX-XXXX -XXXX-XXXX XXXXXXXX.
  • Чтобы различать эти два типа, я добавил параметр flag в функцию создания пар _p8(s), параметр s сообщает функции, добавлять ли тире или нет.
  • В итоге мы создаем GUID со следующей цепочкой: _p8() + _p8(true) + _p8(true) + _p8() и возвращаем его.

Ссылка на этот пост в моем блоге

Наслаждайтесь! : -)

62 голосов
/ 12 декабря 2011

Вот комбинация ответа с наибольшим количеством голосов с обходным путем для столкновений Chrome :

generateGUID = (typeof(window.crypto) != 'undefined' && 
                typeof(window.crypto.getRandomValues) != 'undefined') ?
    function() {
        // If we have a cryptographically secure PRNG, use that
        // https://stackoverflow.com/questions/6906916/collisions-when-generating-uuids-in-javascript
        var buf = new Uint16Array(8);
        window.crypto.getRandomValues(buf);
        var S4 = function(num) {
            var ret = num.toString(16);
            while(ret.length < 4){
                ret = "0"+ret;
            }
            return ret;
        };
        return (S4(buf[0])+S4(buf[1])+"-"+S4(buf[2])+"-"+S4(buf[3])+"-"+S4(buf[4])+"-"+S4(buf[5])+S4(buf[6])+S4(buf[7]));
    }

    :

    function() {
        // Otherwise, just use Math.random
        // https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
            return v.toString(16);
        });
    };

На jsbin , если вы хотите проверить это.

56 голосов
/ 15 ноября 2012

Это полностью несовместимая, но очень производительная реализация для генерации ASCII-безопасного GUID-подобного уникального идентификатора.

function generateQuickGuid() {
    return Math.random().toString(36).substring(2, 15) +
        Math.random().toString(36).substring(2, 15);
}

Генерирует 26 [a-z0-9] символов, получая UID, который является одновременно более коротким и уникальным, чем RFID-совместимые GUID. Тире могут быть добавлены, если удобочитаемость имеет значение.

Здесь приведены примеры использования и время этой функции, а также некоторые другие ответы на этот вопрос. Время было выполнено в Chrome m25, 10 миллионов итераций каждая.

>>> generateQuickGuid()
"nvcjf1hs7tf8yyk4lmlijqkuo9"
"yq6gipxqta4kui8z05tgh9qeel"
"36dh5sec7zdj90sk2rx7pjswi2"
runtime: 32.5s

>>> GUID() // John Millikin
"7a342ca2-e79f-528e-6302-8f901b0b6888"
runtime: 57.8s

>>> regexGuid() // broofa
"396e0c46-09e4-4b19-97db-bd423774a4b3"
runtime: 91.2s

>>> createUUID() // Kevin Hakanson
"403aa1ab-9f70-44ec-bc08-5d5ac56bd8a5"
runtime: 65.9s

>>> UUIDv4() // Jed Schmidt
"f4d7d31f-fa83-431a-b30c-3e6cc37cc6ee"
runtime: 282.4s

>>> Math.uuid() // broofa
"5BD52F55-E68F-40FC-93C2-90EE069CE545"
runtime: 225.8s

>>> Math.uuidFast() // broofa
"6CB97A68-23A2-473E-B75B-11263781BBE6"
runtime: 92.0s

>>> Math.uuidCompact() // broofa
"3d7b7a06-0a67-4b67-825c-e5c43ff8c1e8"
runtime: 229.0s

>>> bitwiseGUID() // jablko
"baeaa2f-7587-4ff1-af23-eeab3e92"
runtime: 79.6s

>>>> betterWayGUID() // Andrea Turri
"383585b0-9753-498d-99c3-416582e9662c"
runtime: 60.0s

>>>> UUID() // John Fowler
"855f997b-4369-4cdb-b7c9-7142ceaf39e8"
runtime: 62.2s

Вот код времени.

var r;
console.time('t'); 
for (var i = 0; i < 10000000; i++) { 
    r = FuncToTest(); 
};
console.timeEnd('t');
56 голосов
/ 15 августа 2011

Вот решение от 9 октября 2011 г. из комментария пользователя jed в https://gist.github.com/982883:

UUIDv4 = function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,b)}

Это выполняет ту же цель, что и текущий ответ с наибольшим рейтингом , но на 50+ меньше байтов, используя принуждение, рекурсию и экспоненциальную запись. Для тех, кому интересно, как это работает, вот аннотированная форма старой версии функции:

UUIDv4 =

function b(
  a // placeholder
){
  return a // if the placeholder was passed, return
    ? ( // a random number from 0 to 15
      a ^ // unless b is 8,
      Math.random() // in which case
      * 16 // a random number from
      >> a/4 // 8 to 11
      ).toString(16) // in hexadecimal
    : ( // or otherwise a concatenated string:
      [1e7] + // 10000000 +
      -1e3 + // -1000 +
      -4e3 + // -4000 +
      -8e3 + // -80000000 +
      -1e11 // -100000000000,
      ).replace( // replacing
        /[018]/g, // zeroes, ones, and eights with
        b // random hex digits
      )
}
...