.NET 짧은 고유 식별자


91

.NET에서 고유 식별자가 필요합니다 (이 경우 GUID가 너무 길기 때문에 사용할 수 없음).

사람들은 여기 에 사용 된 알고리즘 이 좋은 후보 라고 생각 합니까, 아니면 다른 제안이 있습니까?


3
얼마나 짧습니까? 그리고 얼마나 독특합니까? 이더넷 어댑터의 하드웨어 주소를 기반으로하는 경우 GUID는 고유 한 것으로 보장됩니다. 순전히 수학적으로 생산 된 모든 것은 절대로 독특 할 수 없습니다. (천문학적으로 높은 확률로) 아마도 독특 할 것입니다.
— Jon

길이가 15이고 가능한 한 고유합니다 (아마도)
— Noel

4
var random = 4; // 좋아요
— KristoferA

4
(15) 어떤 길이? 15 바이트? 그렇다면 GUID에서 바이트를 제거하지 않는 이유는 무엇입니까 ..?
— KristoferA

3
guid에서 바이트를 제거하면 천문학적으로 키 충돌 가능성이 높아집니다. 순서가 잘못된 바이트를 제거하면 충돌이 발생할 수 있습니다.
— Chris Marisic

답변:


100

이것은 좋은 것입니다-http: //www.singular.co.nz/blog/archive/2007/12/20/shortguid-a-shorter-and-url-friendly-guid-in-c-sharp.aspx

여기에 YouTube와 같은 GUID

Base64를 사용할 수 있습니다.

string base64Guid = Convert.ToBase64String(Guid.NewGuid().ToByteArray());

그러면 E1HKfn68Pkms5zsZsvKONw ==와 같은 문자열이 생성됩니다. GUID는 항상 128 비트이므로 ==를 생략 할 수 있습니다. ==는 항상 끝에 존재하며 22 자 문자열을 제공합니다. 그래도 YouTube만큼 짧지는 않습니다.


11
짧은 메모 : 이것이 URL에 필요한 경우이를 사용하는 사람은 '+'및 '/'문자도 삭제하고 싶을 수 있습니다.
— pootzko


1
Unity에서 UnnyNet에 대해 최대 23 자 길이의 고유 ID를 원했고, 멍청한 GUID에
— 갇혀서

흥미롭게도 (Zoom API로 작업하는 경우) 이것이 거의 확실하게 UUID를 생성하는 방법입니다. 길이는 22 자이며 base64로 인코딩됩니다.
— vgel

39

Dor Cohen과 비슷한 접근 방식을 사용하지만 일부 특수 문자를 제거합니다.

var uid = Regex.Replace(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), "[/+=]", "");     

이것은 영숫자 문자 만 출력합니다. UID의 길이가 항상 동일하다고 보장 할 수는 없습니다. 다음은 샘플 실행입니다.

vmKo0zws8k28fR4V4Hgmw 
TKbhS0G2V0KqtpHOU8e6Ug 
rfDi1RdO0aQHTosh9dVvw
3jhCD75fUWjQek8XRmMg 
CQUg1lXIXkWG8KDFy7z6Ow 
bvyxW5aj10OmKA5KMhppw
pIMK8eq5kyvLK67xtsIDg
VX4oljGWpkSQGR2OvGoOQ 
NOHBjUUHv06yIc7EvotRg
iMniAuUG9kiGLwBtBQByfg

6
이와 같은 정보를 버리면 GUID가 보장하는 일부 속성이 손실됩니다. GUID와 직렬화 형식 사이의 이격을 유지하면서 익숙하지 않은 문자를 다른 문자로 바꾸는 것이 좋습니다.
— Lukáš Lánský

3
이것은 GUID로 다시 변환하고 싶지만 다른 것을 위해 임의의 문자가 필요한 경우 사용하기에 좋은 방법입니다. 예를 들어 다중 스레드에서 작업자 추적 또는 스레드 된 개체에 대한 로깅 접두사 등
— Piotr Kula

23
var ticks = new DateTime(2016,1,1).Ticks;
var ans = DateTime.Now.Ticks - ticks;
var uniqueId = ans.ToString("x");

이러한 ID 생성을 시작할 기준 날짜 (이 경우 2016 년 1 월 1 일)를 유지합니다. 이렇게하면 ID가 작아집니다.

생성 된 수 : 3af3c14996e54


milliseconds해당 DateTime개체에 대해 항상 0입니다
— Teejay

마지막 문장도 제거하십시오.
— Teejay

1
oneliner : var uniqueId = (DateTime.Now.Ticks-new DateTime (2016, 1, 1) .Ticks) .ToString ( "x");
— Sgedda


17

간단하게 사용할 수있는 패키지. 임시 요청 ID 생성기에 사용합니다.

https://www.nuget.org/packages/shortid

https://github.com/bolorundurowb/shortid

용도 System.Random

string id = ShortId.Generate();
// id = KXTR_VzGVUoOY

(github 페이지에서)

숫자, 특수 문자 및 길이를 원하는지 여부를 지정하여 생성 된 ID 유형을 제어하려면 Generate 메서드를 호출하고 세 개의 매개 변수를 전달합니다. 첫 번째는 숫자를 원하는지 여부를 나타내는 부울, 두 번째는 원하는 여부를 나타내는 부울입니다. 특수 문자, 마지막은 선호하는 길이를 나타내는 숫자입니다.

string id = ShortId.Generate(true, false, 12);
// id = VvoCDPazES_w

10

내가 아는 한, GUID의 일부를 제거하는 것이 고유하다고 보장 할 수는 없습니다 . 사실 고유 한 것과는 거리가 멀습니다.

글로벌 고유성을 보장하는 가장 짧은 것은 Jeff Atwood의이 블로그 게시물에 나와 있습니다. 링크 된 게시물에서 그는 GUID를 줄이는 여러 방법에 대해 설명하고 결국 Ascii85 인코딩을 통해 20 바이트로 줄 입니다.

그러나 15 바이트 이하의 솔루션이 절대적으로 필요한 경우 전역 적으로 고유하다고 보장되지 않는 것을 사용하는 것 외에 다른 선택의 여지가 없습니다.


6

IDENTITY 값은 데이터베이스에서 고유해야하지만 제한 사항을 알고 있어야합니다. 예를 들어 대량 데이터 삽입이 기본적으로 불가능 해져서 매우 많은 수의 레코드로 작업하는 경우 속도가 느려집니다.

날짜 / 시간 값을 사용할 수도 있습니다. 날짜 / 시간을 PK로 사용하는 데이터베이스를 여러 개 보았습니다. 매우 깨끗하지는 않지만 작동합니다. 삽입을 제어하면 값이 코드에서 고유하다는 것을 효과적으로 보장 할 수 있습니다.


6

내 로컬 앱의 경우이 시간 기반 접근 방식을 사용하고 있습니다.

/// <summary>
/// Returns all ticks, milliseconds or seconds since 1970.
/// 
/// 1 tick = 100 nanoseconds
/// 
/// Samples:
/// 
/// Return unit     value decimal           length      value hex       length
/// --------------------------------------------------------------------------
/// ticks           14094017407993061       17          3212786FA068F0  14
/// milliseconds    1409397614940           13          148271D0BC5     11
/// seconds         1409397492              10          5401D2AE        8
///
/// </summary>
public static string TickIdGet(bool getSecondsNotTicks, bool getMillisecondsNotTicks, bool getHexValue)
{
    string id = string.Empty;

    DateTime historicalDate = new DateTime(1970, 1, 1, 0, 0, 0);

    if (getSecondsNotTicks || getMillisecondsNotTicks)
    {
        TimeSpan spanTillNow = DateTime.UtcNow.Subtract(historicalDate);

        if (getSecondsNotTicks)
            id = String.Format("{0:0}", spanTillNow.TotalSeconds);
        else
            id = String.Format("{0:0}", spanTillNow.TotalMilliseconds);
    }
    else
    {
        long ticksTillNow = DateTime.UtcNow.Ticks - historicalDate.Ticks;
        id = ticksTillNow.ToString();
    }

    if (getHexValue)
        id = long.Parse(id).ToString("X");

    return id;
}

3

여기 내 솔루션은 동시성, 초당 1000 GUID 및 스레드 안전에 안전하지 않습니다.

public static class Extensors
{

    private static object _lockGuidObject;

    public static string GetGuid()
    {

        if (_lockGuidObject == null)
            _lockGuidObject = new object();


        lock (_lockGuidObject)
        {

            Thread.Sleep(1);
            var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            var epochLong = Convert.ToInt64((DateTime.UtcNow - epoch).TotalMilliseconds);

            return epochLong.DecimalToArbitrarySystem(36);

        }

    }

    /// <summary>
    /// Converts the given decimal number to the numeral system with the
    /// specified radix (in the range [2, 36]).
    /// </summary>
    /// <param name="decimalNumber">The number to convert.</param>
    /// <param name="radix">The radix of the destination numeral system (in the range [2, 36]).</param>
    /// <returns></returns>
    public static string DecimalToArbitrarySystem(this long decimalNumber, int radix)
    {
        const int BitsInLong = 64;
        const string Digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        if (radix < 2 || radix > Digits.Length)
            throw new ArgumentException("The radix must be >= 2 and <= " + Digits.Length.ToString());

        if (decimalNumber == 0)
            return "0";

        int index = BitsInLong - 1;
        long currentNumber = Math.Abs(decimalNumber);
        char[] charArray = new char[BitsInLong];

        while (currentNumber != 0)
        {
            int remainder = (int)(currentNumber % radix);
            charArray[index--] = Digits[remainder];
            currentNumber = currentNumber / radix;
        }

        string result = new String(charArray, index + 1, BitsInLong - index - 1);
        if (decimalNumber < 0)
        {
            result = "-" + result;
        }

        return result;
    }

코드가 최적화되지 않았습니다. 샘플 만 있습니다!.


흥미로운 솔루션이지만 UtcNow밀리 초마다 고유 한 틱 값을 반환 한다는 보장은 없습니다 . 설명 에 따라 해상도는 시스템 타이머에 따라 다릅니다. 또한 시스템 시계가 뒤로 변하지 않는지 확인하는 것이 좋습니다! (user13971889의 답변이이 질문을 내 피드의 맨 위로 올렸고 그 답변을 비판했기 때문에 여기에서 그 비판을 반복해야한다고 생각합니다.)
— Joe Sewell

3

앱에 몇 백만 명의 사용자가없는 경우 동일한 MILLISECOND에서 짧은 고유 문자열을 생성하는 것을 사용하면 아래 함수를 사용할 수 있습니다.

private static readonly Object obj = new Object();
private static readonly Random random = new Random();
private string CreateShortUniqueString()
{
    string strDate = DateTime.Now.ToString("yyyyMMddhhmmssfff");
    string randomString ;
    lock (obj)
    {
        randomString = RandomString(3);
    }
    return strDate + randomString; // 16 charater
}
private string RandomString(int length)
{

    const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxy";
    var random = new Random();
    return new string(Enumerable.Repeat(chars, length)
      .Select(s => s[random.Next(s.Length)]).ToArray());
}

향후 99 년 동안 앱을 사용해야하는 경우 yyyy를 yy로 변경하십시오.
20160511 업데이트 : 임의 기능 수정
-잠금 개체 추가
-RandomString 함수 참조에서 임의 변수 이동


2
매번 새로운 Random을 초기화해서는 안되지만 이는 훌륭합니다. 그 이유 lock는 동일한 Random인스턴스 를 재사용 할 수 있기 때문입니다 . 그 줄을 지우는 걸 잊은 것 같아요!
— NibblyPig

1

게시일과 상당히 멀다는 것을 알고 있습니다 ... :)

9 개의 Hexa 문자 만 생성하는 생성기가 있습니다. 예 : C9D6F7FF3, C9D6FB52C

public class SlimHexIdGenerator : IIdGenerator
{
    private readonly DateTime _baseDate = new DateTime(2016, 1, 1);
    private readonly IDictionary<long, IList<long>> _cache = new Dictionary<long, IList<long>>();

    public string NewId()
    {
        var now = DateTime.Now.ToString("HHmmssfff");
        var daysDiff = (DateTime.Today - _baseDate).Days;
        var current = long.Parse(string.Format("{0}{1}", daysDiff, now));
        return IdGeneratorHelper.NewId(_cache, current);
    }
}


static class IdGeneratorHelper
{
    public static string NewId(IDictionary<long, IList<long>> cache, long current)
    {
        if (cache.Any() && cache.Keys.Max() < current)
        {
            cache.Clear();
        }

        if (!cache.Any())
        {
            cache.Add(current, new List<long>());
        }

        string secondPart;
        if (cache[current].Any())
        {
            var maxValue = cache[current].Max();
            cache[current].Add(maxValue + 1);
            secondPart = maxValue.ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            cache[current].Add(0);
            secondPart = string.Empty;
        }

        var nextValueFormatted = string.Format("{0}{1}", current, secondPart);
        return UInt64.Parse(nextValueFormatted).ToString("X");
    }
}

1

@dorcohen의 답변과 @pootzko의 의견을 기반으로합니다. 이것을 사용할 수 있습니다. 와이어를 통해 안전합니다.

var errorId = System.Web.HttpServerUtility.UrlTokenEncode(Guid.NewGuid().ToByteArray());

궁금한 사람이있는 경우 결과 : Jzhw2oVozkSNa2IkyK4ilA2또는 dotnetfiddle.net/VIrZ8j
— chriszo111

1

다른 일부를 기반으로 URL (및 Docker) 안전하고 정보를 잃지 않는 다른 인코딩 된 GUID를 제공하는 솔루션이 있습니다.

Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Replace("=", "").Replace("+", "-").Replace("/", "_");

출력 예는 다음과 같습니다.

BcfttHA780qMdHSxSBoZFA
_4p5srPgOE2f25T_UnoGLw
H9xR_zdfm0y-zYjdR3NOig

1

C #에서 long값은 64 비트이며 Base64로 인코딩 된 경우 패딩 1 개를 포함하여 12 개의 문자가 있습니다 =. 패딩을 다듬 으면 =11자가됩니다.

여기서 한 가지 미친 아이디어는 Unix Epoch와 하나의 Epoch 값에 대한 카운터를 조합하여 값을 형성 할 long수 있다는 것입니다. C #의 Unix Epoch DateTimeOffset.ToUnixEpochMilliseconds는long 포맷이지만 그렇지 날짜 시간 값이 최대 날짜 시간 값보다 큰 것 때문에 8 바이트의 처음 2 바이트는 항상 0이다. 따라서 ushort카운터 를 배치 할 2 바이트가 됩니다.

따라서 전체적으로 ID 생성 횟수가 밀리 초당 65536 개를 초과하지 않는 한 고유 한 ID를 가질 수 있습니다.

// This is the counter for current epoch. Counter should reset in next millisecond
ushort currentCounter = 123;

var epoch = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
// Because epoch is 64bit long, so we should have 8 bytes
var epochBytes = BitConverter.GetBytes(epoch);
if (BitConverter.IsLittleEndian)
{
    // Use big endian
    epochBytes = epochBytes.Reverse().ToArray();
}

// The first two bytes are always 0, because if not, the DateTime.UtcNow is greater 
// than DateTime.Max, which is not possible
var counterBytes = BitConverter.GetBytes(currentCounter);
if (BitConverter.IsLittleEndian)
{
    // Use big endian
    counterBytes = counterBytes.Reverse().ToArray();
}

// Copy counter bytes to the first 2 bytes of the epoch bytes
Array.Copy(counterBytes, 0, epochBytes, 0, 2);

// Encode the byte array and trim padding '='
// e.g. AAsBcTCCVlg
var shortUid = Convert.ToBase64String(epochBytes).TrimEnd('=');

1
    public static string ToTinyUuid(this Guid guid)
    {
        return Convert.ToBase64String(guid.ToByteArray())[0..^2]  // remove trailing == padding 
            .Replace('+', '-')                          // escape (for filepath)
            .Replace('/', '_');                         // escape (for filepath)
    }

용법

Guid.NewGuid().ToTinyUuid()

다시 전환하는 것은 로켓 과학이 아니므로 그 정도는 남겨 두겠습니다.


0

문자열을 입력 할 필요가 없으면 다음을 사용할 수 있습니다.

static class GuidConverter
{
    public static string GuidToString(Guid g)
    {
        var bytes = g.ToByteArray();
        var sb = new StringBuilder();
        for (var j = 0; j < bytes.Length; j++)
        {
            var c = BitConverter.ToChar(bytes, j);
            sb.Append(c);
            j++;
        }
        return sb.ToString();
    }

    public static Guid StringToGuid(string s) 
        => new Guid(s.SelectMany(BitConverter.GetBytes).ToArray());
}

이렇게하면 Guid가 다음과 같이 8 자 문자열로 변환됩니다.

{b77a49a5-182b-42fa-83a9-824ebd6ab58d}-> "䦥 띺 ᠫ 䋺 ꦃ 亂 檽 趵"

{c5f8f7f5-8a7c-4511-b667-8ad36b446617}-> " 엸 詼 䔑 架 펊 䑫 ᝦ"


0

다음은 임의의 짧은 고유 ID를 생성하는 작은 방법입니다. 안전한 난수 생성을 위해 암호화 rng를 사용합니다. chars문자열에 필요한 문자를 추가하십시오 .

private string GenerateRandomId(int length)
{
    char[] stringChars = new char[length];
    byte[] randomBytes = new byte[length];
    using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
    {
        rng.GetBytes(randomBytes);
    }

    string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";           

    for (int i = 0; i < stringChars.Length; i++)
    {
        stringChars[i] = chars[randomBytes[i] % chars.Length];
    }

    return new string(stringChars);
}

0

문자 (+ /-)를 잃지 않고 URL에 GUID를 사용하려면 base32로 변환해야합니다.

10,000,000의 경우 중복 키 없음

    public static List<string> guids = new List<string>();
    static void Main(string[] args)
    {
        for (int i = 0; i < 10000000; i++)
        {
            var guid = Guid.NewGuid();
            string encoded = BytesToBase32(guid.ToByteArray());
            guids.Add(encoded);
            Console.Write(".");
        }
        var result = guids.GroupBy(x => x)
                    .Where(group => group.Count() > 1)
                    .Select(group => group.Key);

        foreach (var res in result)
            Console.WriteLine($"Duplicate {res}");

        Console.WriteLine($"*********** end **************");
        Console.ReadLine();
    }

    public static string BytesToBase32(byte[] bytes)
    {
        const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        string output = "";
        for (int bitIndex = 0; bitIndex < bytes.Length * 8; bitIndex += 5)
        {
            int dualbyte = bytes[bitIndex / 8] << 8;
            if (bitIndex / 8 + 1 < bytes.Length)
                dualbyte |= bytes[bitIndex / 8 + 1];
            dualbyte = 0x1f & (dualbyte >> (16 - bitIndex % 8 - 5));
            output += alphabet[dualbyte];
        }

        return output;
    }


-1
private static readonly object _getUniqueIdLock = new object();
public static string GetUniqueId()
{       
    lock(_getUniqueIdLock)
    {
        System.Threading.Thread.Sleep(1);
        return DateTime.UtcNow.Ticks.ToString("X");
    }
}

1
흥미로운 솔루션이지만 UtcNow밀리 초마다 고유 한 틱 값을 반환 한다는 보장은 없습니다 . 설명 에 따라 해상도는 시스템 타이머에 따라 다릅니다. 또한 시스템 시계가 뒤로 변하지 않는지 확인하는 것이 좋습니다! (ur3an0의 답변에도 이러한 문제가 있습니다.)
— Joe Sewell

동의합니다. 이것은 가난한 사람의 접근 방식이며 자신의 잘 통제 된 환경을 넘어 사용해서는 안됩니다.
— user13971889

-2

당신이 사용할 수있는

code = await UserManager.GenerateChangePhoneNumberTokenAsync(input.UserId, input.MobileNumber);

그 6좋은 문자 만 599527,143354

사용자가 간단하게 가상화하면

var result = await UserManager.VerifyChangePhoneNumberTokenAsync(input.UserId, input.Token, input.MobileNumber);

이것이 당신을 도울 수 있기를 바랍니다


나는 항상 암호를 간단하고 기억하기 쉽게 유지합니다.
— Toolkit

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.