如何创建 GUID/UUID

IT技术 javascript guid uuid
2020-12-31 00:28:20

我正在尝试在 JavaScript 中创建全局唯一标识符。我不确定所有浏览器上都有哪些例程,内置随机数生成器的“随机”和种子如何等等。

GUID / UUID 应至少为 32 个字符,并且应保持在 ASCII 范围内以避免在传递它们时出现问题。

6个回答

[编辑 2021-10-16 以反映生成 RFC4122-complaint UUID 的最新最佳实践]

这里的大多数读者都希望使用uuidmodule它经过充分测试和支持。

crypto.randomUUID()功能是在支持新兴的标准Node.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());

注意:强烈建议不要使用依赖于 Math.random()任何UUID 生成器(包括本答案以前版本中的片段),原因在此处 有最佳解释TL;DR:基于 Math.random() 的解决方案不提供良好的唯一性保证。

如果你真的想保持版本控制内联,而不是落后于修订历史,你必须颠倒顺序:保持最新的答案作为第一。
2021-02-12 00:28:20
我有点困惑,在javascript中[1e7]+-1e3并没有真正的意义,一个数组被添加到一个数字上?我错过了什么?注意:在typescript中它没有通过
2021-02-18 00:28:20
@DrewNoakes - UUID 不仅仅是一串完全随机的#。“4”是 uuid 版本(4 =“随机”)。“y”标记需要嵌入 uuid 变体(基本上是字段布局)的位置。有关更多信息,请参阅ietf.org/rfc/rfc4122.txt 的第 4.1.1 和 4.1.3 节
2021-02-26 00:28:20
@Muxa 的问题的答案肯定是“不”吗?信任来自客户的东西从来都不是真正安全的。我想这取决于您的用户打开 javascript 控制台并手动将变量更改为他们想要的东西的可能性。或者他们可以只给你发回他们想要的 id。它还取决于用户选择自己的 ID 是否会导致漏洞。无论哪种方式,如果它是进入表的随机数 ID,我可能会在服务器端生成它,以便我知道我可以控制这个过程。
2021-02-28 00:28:20
我知道你已经在你的帖子中添加了很多警告,但是你现在最好只删除第一个答案,很多菜鸟只会来到这个答案并复制他们看到的第一件事而不阅读其余部分。实际上,您无法从 Math.random API 可靠地生成 UUID,依赖它会很危险。
2021-03-04 00:28:20

UUID(通用唯一标识符),也称为 GUID(全局唯一标识符),根据RFC 4122,是旨在提供某些唯一性保证的标识符。

虽然可以在几行 JavaScript 代码中实现符合 RFC 的 UUID(例如,请参阅下面的@broofa 的回答),但有几个常见的陷阱:

  • 无效的 id 格式(UUID 必须采用“ xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx形式,其中 x 是 [0-9, af] 之一M是 [1-5] 之一,N是 [8, 9, a 或 b]
  • 使用低质量的随机源(例如Math.random

因此,鼓励为生产环境编写代码的开发人员使用严格的、维护良好的实现,例如uuidmodule。

这不应该是公认的答案。它实际上并没有回答这个问题——而是鼓励导入 25,000 行代码,你可以在任何现代浏览器中用一行代码来做一些事情。
2021-02-17 00:28:20
我质疑 Math.random 的随机性质量如此之低的说法。v8.dev/blog/math-random如您所见,它通过了一个很好的测试套件,并且 v8、FF 和 Safari 使用了相同的算法。并且 RFC 声明,伪随机数对于 UUID 是可以接受的
2021-02-22 00:28:20
实际上,RFC 允许使用从随机数创建的 UUID。你只需要摆弄几位就可以识别它。见第 4.4 节。从真正随机数或伪随机数创建 UUID 的算法: rfc-archive.org/getrfc.php?rfc=4122
2021-02-25 00:28:20
@Phil 这是一个“非常活跃的问题”,这意味着它应该有一个带有绿色勾号的优秀答案。不幸的是,情况并非如此。这个答案没有任何错误或不正确(如果有,我会编辑答案) - 但下面存在另一个更好的答案,我认为它应该在列表的顶部。此外,这个问题特别与浏览器中的 javascript 相关,而不是 node.js。
2021-03-02 00:28:20
@AbhiBeckert 答案是从 2008 年开始的,对于 node.js 项目,选择依赖项而不是项目大小可能更有效
2021-03-08 00:28:20

我真的很喜欢Broofa 的答案多么干净,但不幸的是,糟糕的实现Math.random留下了碰撞的机会。

这是一个类似的符合RFC4122版本 4 的解决方案,它通过将前 13 个十六进制数字偏移时间戳的十六进制部分来解决该问题,并且一旦从页面加载开始以微秒的十六进制部分耗尽偏移量。这样,即使Math.random在同一个种子上,两个客户端也必须在页面加载后(如果支持高性能时间)和完全相同的毫秒(或 10,000 多年后)生成完全相同的微秒数以获得相同的 UUID:

function generateUUID() { // Public Domain/MIT
    var d = new Date().getTime();//Timestamp
    var d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now()*1000)) || 0;//Time in microseconds since page-load or 0 if unsupported
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random() * 16;//random number between 0 and 16
        if(d > 0){//Use timestamp until depleted
            r = (d + r)%16 | 0;
            d = Math.floor(d/16);
        } else {//Use microseconds since page-load if supported
            r = (d2 + r)%16 | 0;
            d2 = Math.floor(d2/16);
        }
        return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });
}

var onClick = function(){
    document.getElementById('uuid').textContent = generateUUID();
}
onClick();
#uuid { font-family: monospace; font-size: 1.5em; }
<p id="uuid"></p>
<button id="generateUUID" onclick="onClick();">Generate UUID</button>

这是一个要测试的小提琴。


ES6 的现代化片段

Crypto.getRandomValues修正了的Math.random的主要问题?
2021-02-15 00:28:20
实际时间分辨率可能是也可能不是 17 毫秒(1/60 秒),而不是 1 毫秒。
2021-02-24 00:28:20
@NaveenReddyMarthala Node.js 默认在严格模式下运行 JavaScript,遗憾的是不允许布尔逻辑运算符速记检查undefined变量的真实性要解决此问题,请尝试在更新版本中替换var d2 = (performance ..var d2 = (typeof performance !== 'undefined' ..as。另一种选择(实际上将利用 Node.js 提高的性能精度而不是将其丢弃)是重新添加const { performance } = require('perf_hooks');您的需求。
2021-03-03 00:28:20
请记住,new Date().getTime()不是每毫秒更新一次。我不确定这如何影响算法的预期随机性。
2021-03-08 00:28:20
性能。现在会更好。与 Date.now 不同的是,返回的时间戳performance.now()不限于一毫秒的分辨率。相反,它们将时间表示为精度高达微秒的浮点数与 Date.now 不同的是,performance.now() 返回的值总是以恒定速率增加,独立于可能手动调整或由网络时间协议等软件调整的系统时钟。
2021-03-09 00:28:20

broofa 的答案非常巧妙,确实 - 令人印象深刻的聪明,真的......符合 RFC4122,有点可读且紧凑。惊人的!

但是,如果您正在查看该正则表达式、那么多replace()回调、toString()' 和Math.random()函数调用(他只使用结果的四位并浪费了其余部分),您可能会开始怀疑性能。事实上,joelpt 甚至决定用generateQuickGUID.

但是,我们能否获得速度RFC 合规性?我说是!我们能保持可读性吗?嗯......不是真的,但如果你跟着做,这很容易。

但首先,与 broofa 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.

因此,通过我的第 6 次优化迭代,我击败了最受欢迎的答案12 倍以上,超过了9 倍的接受答案,以及2-3 倍的快速不合规答案而且我仍然符合 RFC 4122。

有兴趣怎么办?我已经把完整的源代码放在http://jsfiddle.net/jcward/7hyaC/3/http://jsperf.com/uuid-generator-opt/4

为了说明,让我们从broofa的代码开始:

function broofa() {
    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(broofa())

所以它替换x为任何随机的十六进制数字,y随机数据(除了10根据 RFC 规范强制前两位),并且正则表达式与-4字符不匹配,因此他不必处理它们。非常非常光滑。

首先要知道的是,函数调用是昂贵的,正则表达式也是如此(尽管他只使用了 1 个,但它有 32 个回调,每个匹配一个,并且在 32 个回调中的每一个中它调用 Math.random() 和 v。 toString(16))。

提高性能的第一步是消除 RegEx 及其回调函数,并改用简单的循环。这意味着我们必须处理-4字符,而 broofa 则没有。另外,请注意,我们可以使用字符串数组索引来保持他流畅的字符串模板架构:

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

console.log(e1())

基本上,相同的内部逻辑,除了我们检查-or4和使用 while 循环(而不是replace()回调)使我们获得了近 3 倍的改进!

下一步是桌面上的一个小步骤,但在移动设备上有很大的不同。让我们减少 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
}

console.log(e2())

根据平台的不同,这可以为我们节省 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
}

console.log(e4())

接下来的优化是另一个经典。由于我们在每次循环迭代中只处理四位输出,让我们将循环次数减半并在每次迭代中处理八位。这很棘手,因为我们仍然必须处理符合 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
}

console.log(e5())

我尝试了一次处理 16 位的 e6(),仍然使用 256 个元素的LUT,它显示优化的收益递减。虽然它的迭代次数较少,但由于处理量的增加,内部逻辑变得复杂,它在桌面上的表现也是如此,而在移动设备上的速度只有约 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];
}

console.log(e7())

module化:http ://jcward.com/UUID.js -UUID.generate()

有趣的是,生成 16 字节的随机数据是容易的部分。整个技巧是用符合 RFC 的字符串格式表达它,并且它最紧密地使用 16 字节的随机数据、一个展开的循环和查找表来实现。

我希望我的逻辑是正确的——在这种单调乏味的工作中很容易出错。但输出对我来说看起来不错。我希望你喜欢这个疯狂的代码优化之旅!

请注意:我的主要目标是展示和教授潜在的优化策略。其他答案涵盖了重要的主题,例如碰撞和真正的随机数,这对于生成良好的 UUID 很重要。

我觉得您的比较可能不公平,因为 broofa 的回答似乎是针对 e4 UUID,而您在此处针对 Ward 的 e7 实现进行了测试。当您将 broofa 的答案与此处提供的 e4 版本进行比较时,此答案会更快。
2021-02-09 00:28:20
@安迪是对的。Broofa 的代码在 2021 年 8 月更快。我实施了 Dave 的建议并自己运行了测试。但我不认为差异在生产中应该很重要:jsbench.github.io/#80610cde9bc93d0f3068e5793e60ff11
2021-02-11 00:28:20
我不知道@Broofa 的答案自这些测试运行以来是否发生了变化(或者运行测试的浏览器引擎是否发生了变化 - 已经五年了),但我只是在两个不同的基准测试服务 (jsben.ch) 上运行它们和 jsbench.github.io),并且在每种情况下 Broofa 的答案(使用 Math.random)都比这个 e7() 版本快 30 - 35%。
2021-02-22 00:28:20
我只能说 - 我无法计算我已经向开发人员指出这个答案多少次,因为它非常漂亮地指出了性能、代码优雅和可读性之间的权衡。谢谢杰夫。
2021-03-05 00:28:20
这段代码仍然包含一些错误:这些Math.random()*0xFFFFFFFF行应该是Math.random()*0x100000000完全随机的,并且>>>0应该被用来代替|0保持未签名的值(尽管使用当前代码我认为即使它们被签名也可以离开)。最后,window.crypto.getRandomValues如果可用的话,现在使用是一个非常好的主意,并且只有在绝对必要时才回退到 Math.random。Math.random 的熵很可能少于 128 位,在这种情况下,这将更容易发生不必要的冲突。
2021-03-07 00:28:20

用:

let uniqueId = Date.now().toString(36) + Math.random().toString(36).substring(2);

如果 ID 的生成间隔超过 1 毫秒,则它们是 100% 唯一的。

如果以较短的间隔生成两个 ID,并假设随机方法是真正随机的,则生成的 ID 有 99.99999999999999% 的可能性是全局唯一的(10^15 中的 1 个发生冲突)。

您可以通过添加更多数字来增加此数字,但要生成 100% 唯一 ID,您将需要使用全局计数器。

如果您需要 RFC 兼容性,此格式将作为有效的第 4 版 GUID 传递:

let u = Date.now().toString(16) + Math.random().toString(16) + '0'.repeat(16);
let guid = [u.substr(0,8), u.substr(8,4), '4000-8' + u.substr(13,3), u.substr(16,12)].join('-');

上面的代码遵循意图,但不是RFC的字母。在其他差异中,有几个随机数字短。(如果需要,添加更多随机数字)好处是这真的很快:) 你可以在这里测试你的 GUID 的有效性

基于@SephReed 的评论,我认为首先包含日期部分很好,因为它按时间顺序排序,如果存储或索引 ID 可能会在以后提供好处。
2021-02-08 00:28:20
这不是 UUID 吗?
2021-02-16 00:28:20
不。UUID/GUID 是一个 122 位(+ 6 个保留位)的数字。它可能会通过全局计数器服务来保证唯一性,但通常它会按时间、MAC 地址和随机性进行中继。UUID 不是随机的!我在这里建议的 UID 没有完全压缩。您可以将其压缩为 122 位整数,添加 6 个预定义位和额外的随机位(删除一些计时器位),最终得到一个完美形成的 UUID/GUID,然后您必须将其转换为十六进制。对我来说,除了符合 ID 的长度外,并没有真正增加任何其他内容。
2021-02-19 00:28:20
在虚拟机上中继 MAC 地址的唯一性是一个坏主意!
2021-02-20 00:28:20
我做这样的事情,但有前导字符和一些破折号(例如[slug, date, random].join("_")create usr_1dcn27itd_hj6onj6phr。这使得 id 也可以作为“创建于”字段
2021-02-24 00:28:20