[javascript] GUID / UUID를 만드는 방법?

JavaScript로 전역 고유 식별자를 만들려고합니다. 모든 브라우저에서 어떤 루틴을 사용할 수 있는지, 내장 난수 생성기가 어떻게 “랜덤”되고 시드되었는지 잘 모르겠습니다.

GUID / UUID는 32 자 이상이어야하며 ASCII 범위를 유지하여 전달할 때 문제가 발생하지 않도록해야합니다.



답변

RFC 4122 에 따르면 GUID (Globally Unique IDentifier)라고도하는 UUID (Universally Unique IDentifier) 는 특정 고유성을 보장하도록 설계된 식별자입니다.

몇 줄의 JS에서 RFC 호환 UUID를 구현할 수 있지만 (예 : @broofa의 답변 참조) 몇 가지 일반적인 함정이 있습니다.

  • 잘못된 ID 형식 (UUID는 ” xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx” 형식이어야합니다 . 여기서 x는 [0-9, af] 중 하나임) 중 하나이고 M 은 [1-5] 중 하나이고 N 은 [8, 9, a 또는 b] 임 )
  • 난수의 낮은 품질의 소스 코드의 사용 (예 Math.random)

따라서 프로덕션 환경을위한 코드를 작성하는 개발자는 uuid 모듈 과 같이 엄격하고 잘 관리 된 구현을 사용하는 것이 좋습니다 .


답변

을 위해 RFC4122의 버전 4 준수 솔루션이 한 줄 (틱) 솔루션은 내가 가지고 올 수있는 가장 컴팩트 :

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()은 간결성을 위해 사용 되지만 고품질 RNG Math.random()아닙니다 . 자세한 내용은 Adam Hyland의 Math.random ()에 대한 훌륭한 글 을 참조하십시오. 보다 강력한 솔루션을 위해서는 uuid 모듈 사용을 고려하십시오 위해서는 고품질 RNG API를 사용 .

업데이트, 2015년 8월 26일는 : 사이드 참고로,이 요점은 충돌의 특정 확률에 도달하기 전에 생성 할 수 있습니다 얼마나 많은 ID를 확인하는 방법에 대해 설명합니다. 예를 들어 3.26×10 15 버전 4 RFC4122 UUID를 사용하면 1 백만 분의 1의 충돌 가능성이 있습니다.

2017-06-28 업데이트 : Chrome 개발자 가 Chrome, Firefox 및 Safari의 Math.random PRNG 품질 상태를 논의한 좋은 기사입니다 . tl; dr-2015 년 말 기준으로 “아주 훌륭”하지만 암호화 품질은 아닙니다. 이 문제를 해결하기 위해 ES6, cryptoAPI 및 약간의 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());

2020-01-06 업데이트 : JS 언어의 일부로 표준 모듈 작업에 대한 제안이uuid 있습니다.


답변

나는 Broofa의 대답 이 얼마나 깨끗한 정말 좋아 하지만 불행히도 구현Math.random잘못 되면 충돌의 가능성이 있습니다.

다음 은 타임 스탬프의 16 진 부분으로 처음 13 개의 16 진수를 오프셋하고 페이지로드 이후 마이크로 초의 16 진 부분으로 오프셋을 고갈 시켜서이 문제를 해결 하는 유사한 RFC4122 버전 4 호환 솔루션입니다. 이렇게 Math.random하면 동일한 시드에 있더라도 두 클라이언트 모두 페이지로드 이후 (고성능 시간이 지원되는 경우) 이후 정확히 동일한 밀리 초 (또는 10,000 년 이상) 후 UUID를 정확히 동일한 수의 마이크로 초로 생성해야합니다. 동일한 UUID를 얻으십시오.

function generateUUID() { // Public Domain/MIT
    var d = new Date().getTime();//Timestamp
    var d2 = (performance && 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);
    });
}

console.log(generateUUID())

테스트 할 바이올린이 있습니다.


답변

broofa의 대답은 실제로 매끄럽고 매끄 럽습니다 .rfc4122 호환, 다소 읽기 쉽고 컴팩트합니다. 대박!

그러나 그 정규 표현식, 많은 replace()콜백, toString()Math.random()함수 호출 (그가 4 비트의 결과 만 사용하고 나머지는 낭비하는 곳)을 보고 있다면 성능에 대해 궁금해 할 수 있습니다. 실제로 joelpt는로 일반 GUID 속도를 위해 RFC를 포기하기로 결정했습니다 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의 코드로 시작해 봅시다 :

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임의의 16 진수로, y임의의 데이터 ( 10RFC 사양 에 따라 상위 2 비트를 강제로 제외)를 대체 하며 정규 표현식은 문자 -또는 4문자 와 일치 하지 않으므로 처리 할 필요가 없습니다. 매우 매끄 럽습니다.

가장 먼저 알아야 할 것은 정규 표현식과 마찬가지로 함수 호출이 비싸다는 것입니다 (1을 사용하지만 각 일치에 대해 하나씩 32 개의 콜백이 있으며 각 32 개의 콜백에서 Math.random () 및 v를 호출합니다. toString (16)).

성능을 향한 첫 단계는 RegEx와 콜백 함수를 제거하고 대신 간단한 루프를 사용하는 것입니다. 이것은 broofa가 아닌 반면에 -and 를 처리해야 함을 의미 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;
}

console.log(e1())

기본적으로, 동일한 내부 로직, 우리는 제외시켰다 확인 -하거나 4, 잠시 루프를 사용하여 (대신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())

다음 최적화는 또 다른 고전입니다. 각 루프 반복에서 4 비트의 출력 만 처리하므로 루프 수를 반으로 줄이고 각 반복마다 8 비트를 처리하겠습니다. 여전히 RFC 호환 비트 위치를 처리해야하기 때문에 까다로울 수 있지만 그렇게 어렵지는 않습니다. 그런 다음 0x00-0xff를 저장하기 위해 더 큰 조회 테이블 (16×16 또는 256)을 만들어야하며 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())

나는 여전히 256 요소 LUT를 사용하여 한 번에 16 비트를 처리하는 e6 ()을 시도했으며 최적화의 감소 수익을 보여주었습니다. 반복 횟수는 적었지만 내부 로직은 처리 증가로 인해 복잡해졌으며 데스크톱에서도 동일하게 수행되었으며 모바일에서는 ~ 10 % 더 빨랐습니다.

적용 할 최종 최적화 기술-루프를 전개하십시오. 우리는 고정 된 횟수로 반복하기 때문에 기술적으로 이것을 모두 손으로 작성할 수 있습니다. 나는 다시 할당하고 성능을 향상시키는 단일 무작위 변수 r로 이것을 한 번 시도했다. 그러나 4 개의 변수에 임의의 데이터를 미리 할당 한 다음 조회 테이블을 사용하고 적절한 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())

Modualized : http://jcward.com/UUID.jsUUID.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;
}


답변

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

ID가 1 밀리 초 이상 생성되면 100 % 고유합니다.

짧은 간격으로 두 개의 ID가 생성되고 임의 방법이 실제로 무작위라고 가정하면 전 세계적으로 고유 할 가능성이있는 99.99999999999999 % 인 ID를 생성합니다 (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의 유효성을 테스트 할 수 있습니다.


답변

형식에서 문자열 생성기 방식과 같은 가장 빠른 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.
 * @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()함수는 소수점 이하 자릿수 (예 🙂 뒤에 16 자리의 0과 1 사이의 10 진수를 반환합니다 0.4363923368509859.
  • 그런 다음이 숫자를 가져 와서 밑 수가 16 인 문자열로 변환합니다 (위의 예에서
    0.6fb7687f).
    Math.random().toString(16).
  • 그런 다음 0.접두사 ( 0.6fb7687f=>
    6fb7687f)를 잘라 내고 8 개의 16 진 문자로 된 문자열을 얻습니다.
    (Math.random().toString(16).substr(2,8).
  • 때때로 Math.random()함수는 0.4363끝에서 0으로 인해 더 짧은 숫자 (예 :)를 반환 합니다 (위의 예에서 실제로 숫자는 0.4363000000000000). 그렇기 때문에이 문자열 "000000000"(0이 0 인 문자열)에 추가 한 다음 substr()9 개의 문자를 정확하게 만드는 기능으로 잘라냅니다 (오른쪽에 0을 채 웁니다).
  • 정확히 9 개의 0을 추가하는 이유는 Math.random()함수가 정확히 0 또는 1을 반환 할 때 (각각에 대해 1 / 10 ^ 16의 확률) 더 나쁜 경우 시나리오 때문입니다 . 그렇기 때문에 9 개의 0을 추가 ( "0"+"000000000"또는 "1"+"000000000") 한 다음 길이가 8자인 두 번째 색인 (3 번째 문자)에서 잘라 내야했습니다. 나머지 경우에는 0을 추가해도 결과가 잘 리므로 결과에 영향을 미치지 않습니다.
    Math.random().toString(16)+"000000000").substr(2,8).

어셈블리 :

  • GUID의 형식은 다음과 같습니다 XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.
  • I는 4 개, 2 종 (또는 포맷)으로 나누어 각각의 부분으로 분할 GUID : XXXXXXXX-XXXX-XXXX.
  • 이제이 두 가지 유형을 사용하여 GUID를 빌드하여 다음과 같이 4 조각으로 GUID를 조립합니다 XXXXXXXX -XXXX-XXXX -XXXX-XXXX XXXXXXXX.
  • 이 두 가지 유형을 구별하기 위해 쌍 생성자 함수에 플래그 매개 변수를 추가했습니다 _p8(s).이 s매개 변수는 함수에 대시를 추가할지 여부를 알려줍니다.
  • 결국 다음 체인을 사용하여 GUID를 빌드 _p8() + _p8(true) + _p8(true) + _p8()하고 반환합니다.

내 블로그에서이 게시물에 링크

즐겨! 🙂