我正在尝试在JavaScript中创建全局唯一标识符。我不确定所有浏览器上可用的例程,“随机”和内置随机数生成器的种子等等。
GUID / UUID应至少为32个字符,并应保持在ASCII范围内,以避免传递它们时出现问题。
我正在尝试在JavaScript中创建全局唯一标识符。我不确定所有浏览器上可用的例程,“随机”和内置随机数生成器的种子等等。
GUID / UUID应至少为32个字符,并应保持在ASCII范围内,以避免传递它们时出现问题。
在这方面有几次尝试。问题是:你想要实际的GUID,还是只需要随机数 看 像GUIDs?生成随机数很容易。
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}
但是,请注意这样的值 不是真正的GUID。
无法在Javascript中生成真正的GUID,因为它们依赖于浏览器不公开的本地计算机的属性。您需要使用ActiveX等特定于操作系统的服务: http://p2p.wrox.com/topicindex/20339.htm
编辑:不正确 - RFC4122允许随机(“版本4”)GUID。有关细节,请参阅其他答案。
注意:提供的代码段不遵循需要该版本的RFC4122(4
)必须集成到生成的输出字符串中。 不要使用这个答案 如果您需要兼容的GUID。
使用:
var uuid = guid();
function guid() {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
document.getElementById('jsGenId').addEventListener('click', function() {
document.getElementById('jsIdResult').value = guid();
})
input { font-family: monospace; }
<button id="jsGenId" type="button">Generate GUID</button>
<br>
<input id="jsIdResult" type="text" placeholder="Results will be placed here..." readonly size="40"/>
为 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()
是 不 保证是高品质的RNG。见亚当海兰德 Math.random上的优秀写作() 详情。对于更强大的解决方案,请考虑类似的问题 uuid模块[免责声明:我是作者],它使用更高质量的RNG API。
更新,2015-08-26:作为旁注,这个 要旨 描述了如何确定在达到一定的碰撞概率之前可以生成多少个ID。例如,使用3.26x1015 版本4 RFC4122 UUID,你有一个百万分之一的碰撞机会。
更新,2017-06-28: 一个 来自Chrome开发人员的好文章 讨论Chrome,Firefox和Safari中Math.random PRNG质量的状态。 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());
我真的很喜欢干净 Broofa的回答 是,但不幸的是,糟糕的实施 Math.random
留下碰撞的机会。
这是一个类似的 RFC4122 符合版本4的解决方案,通过将前13个十六进制数字偏移时间戳的十六进制部分来解决该问题。就这样,即使 Math.random
在同一种子上,两个客户端都必须在完全相同的毫秒(或10,000多年后)生成UUID才能获得相同的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);
});
}
broofa的答案非常光滑,确实 - 令人印象深刻的聪明,真的... rfc4122兼容,有点可读,紧凑。真棒!
但如果你正在看那个正则表达式那么多 replace()
回调, toString()
和 Math.random()
函数调用(他只使用4位结果并浪费其余部分),你可能开始怀疑性能。实际上,joelpt甚至决定将RFC用于通用GUID速度 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次优化迭代,我击败了最受欢迎的答案 12X,接受的答案 9X,以及快速不合规的答案 2-3X。我仍然符合rfc4122标准。
对如何感兴趣?我把完整的资源放在了 http://jsfiddle.net/jcward/7hyaC/3/ 并且 http://jsperf.com/uuid-generator-opt/4
要解释一下,让我们从broofa的代码开始:
'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()
回调)让我们几乎提高了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
}
这节省了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
}
我尝试了一次处理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];
}
Modualized: http://jcward.com/UUID.js - UUID.generate()
有趣的是,生成16字节的随机数据很容易。整个技巧是以符合RFC的字符串格式表达它,并且用16字节的随机数据,一个展开的循环和查找表来完成它。
我希望我的逻辑是正确的 - 在这种繁琐的工作中犯错误很容易。但输出对我来说很好。我希望你通过代码优化享受这种疯狂的旅程!
被告知: 我的主要目标是展示和教授潜在的优化策略。其他答案涵盖了重要的主题,如冲突和真正的随机数,这对于生成良好的UUID非常重要。
这是基于的一些代码 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;
}
最快的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位数(for
例 0.4363923368509859
)。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"
),然后从长度为8个字符的第二个索引(第3个字符)中删除它。对于其他情况,添加零不会损害结果,因为它无论如何都会切断它。Math.random().toString(16)+"000000000").substr(2,8)
。大会:
XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
。XXXXXXXX
和 -XXXX-XXXX
。XXXXXXXX
-XXXX-XXXX
-XXXX-XXXX
XXXXXXXX
。_p8(s)
, s
参数告诉函数是否添加破折号。_p8() + _p8(true) + _p8(true) + _p8()
,并返回它。请享用! :-)
var uniqueId = Math.random().toString(36).substring(2)
+ (new Date()).getTime().toString(36);
如果ID的生成间隔超过1毫秒,则它们是100%唯一的。
如果以较短的间隔生成两个ID,并且假设随机方法是真正随机的,那么这将产生99.99999999999999%可能是全局唯一的ID(在10 ^ 15中的1中发生冲突)
您可以通过添加更多数字来增加此数字,但要生成100%唯一ID,您需要使用全局计数器。
document.getElementById("unique").innerHTML =
Math.random().toString(36).substring(2) + (new Date()).getTime().toString(36);
<div id="unique">
</div>
这是一个组合 最高投票的答案,解决方法 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上 如果你想测试它。