.NET Kısa Benzersiz Tanımlayıcı


91

.NET'te benzersiz bir tanımlayıcıya ihtiyacım var (bu durum için çok uzun olduğu için GUID kullanılamıyor).

İnsanlar burada kullanılan algoritmanın iyi bir aday olduğunu düşünüyor mu veya başka önerileriniz var mı?


6
Ne kadar kısa? Ve ne kadar eşsiz? GUID, bir ethernet adaptörünün donanım adresine dayanıyorsa benzersiz olduğu garanti edilir; tamamen matematiksel olarak üretilen herhangi bir şey asla kanıtlanacak şekilde benzersiz olamaz - sadece muhtemelen benzersiz (astronomik olarak yüksek bir olasılıkla).
Jon

15 uzunlukta ve olabildiğince benzersiz (muhtemelen)
Noel

4
var random = 4; // yeterince iyi
KristoferA

5
15 ne uzunlukta? 15 bayt mı? Öyleyse, neden bir bayt'ı kılavuzdan ayırmıyorsunuz ..?
KristoferA

3
@KristoferA, bir kılavuzda bir baytın çıkarılması, önemli çarpışma şansınızı astronomik olarak artırır. Yanlış sıralı baytı çıkarırsanız, çarpışması kesin hale gelebilir.
Chris Marisic

Yanıtlar:


100

Bu iyi bir - http://www.singular.co.nz/blog/archive/2007/12/20/shortguid-a-shorter-and-url-friendly-guid-in-c-sharp.aspx

ve ayrıca burada YouTube benzeri GUID

Base64'ü kullanabilirsiniz:

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

Bu, E1HKfn68Pkms5zsZsvKONw == gibi bir dize oluşturur. Bir GUID her zaman 128 bit olduğundan, her zaman sonunda bulunacağını bildiğiniz == karakterini atlayabilirsiniz ve bu size 22 karakterlik bir dizge verecektir. Bu, YouTube kadar kısa değil.


11
Kısa bir not: URL'ler için buna ihtiyaç duyulursa, bunu kullanan kişi "+" ve "/" karakterlerini de sterilize etmek isteyebilir
pootzko


1
UnnyNet'te UnnyNet için maksimum 23 karakter uzunluğunda benzersiz bir kimlik istedim ve büyük aptal GUID'lerimle sıkışıp kaldım ve beni çok mutlu ettin :)
nipunasudha

İlginç bir şekilde (Zoom API ile çalışıyorsanız), bu neredeyse kesinlikle UUID'lerini nasıl oluşturduklarıdır. 22 karakter uzunluğundadırlar ve base64 kodludurlar.
vgel

40

Dor Cohen'inkine benzer bir yaklaşım kullanıyorum ancak bazı özel karakterleri kaldırıyorum:

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

Bu sadece alfanümerik karakterlerin çıktısını alacaktır. UID'lerin her zaman aynı uzunlukta olması garanti edilmez. İşte bir örnek çalışma:

vmKo0zws8k28fR4V4Hgmw 
TKbhS0G2V0KqtpHOU8e6Ug 
rfDi1RdO0aQHTosh9dVvw
3jhCD75fUWjQek8XRmMg 
CQUg1lXIXkWG8KDFy7z6Ow 
bvyxW5aj10OmKA5KMhppw
pIMK8eq5kyvLK67xtsIDg
VX4oljGWpkSQGR2OvGoOQ 
NOHBjUUHv06yIc7EvotRg
iMniAuUG9kiGLwBtBQByfg

7
Bunun gibi bilgileri atarak GUID'ler tarafından garanti edilen bazı özellikleri kaybedersiniz. GUID'ler ve serileştirme formatları arasındaki bijeksiyonu korurken, rahat olmadığınız karakterleri farklı karakterlerle değiştirmenizi tavsiye ederim.
Lukáš Lánský

3
Bu dont bir guıd geri dönüştürmek istiyorum ama başka bir şey için rasgele karakterler gerek sadece eğer çok konuda örneğin .. izleme işçileri kullanabilir veya vb dişli nesneler için Önek Logging için güzel biridir
Piotr Kula

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

Bu kimlikleri oluşturmaya başlayacağınız zamandan itibaren (bu durumda 1 Ocak 2016 olan) bir temel tarih tutun. Bu, kimliklerinizi küçültecek.

Oluşturulan Numara: 3af3c14996e54


millisecondso DateTimenesne için her zaman 0'dır
Teejay

Ayrıca son cümleyi de kaldırın.
Teejay

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

6
Bir for-loop.eg dotnetfiddle.net/L3MIgZ
Jaider

17

Basit kullanılabilir paket. Geçici istek kimliği oluşturucu için kullanıyorum.

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

https://github.com/bolorundurowb/shortid

Kullanımlar System.Random

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

(github sayfasından)

Sayılar, özel karakterler ve uzunluk isteyip istemediğinizi belirleyerek oluşturulan kimlik türünü kontrol etmek istiyorsanız, Generate yöntemini çağırın ve üç parametreyi iletin, ilki sayı isteyip istemediğinizi belirten bir boole, ikincisi isteyip istemediğinizi belirten bir boole özel karakterler, sonuncusu uzunluk tercihinizi belirten bir sayıdır.

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

11

Bildiğim kadarıyla , bir GUID'in bir kısmını çıkarmanın benzersiz olacağı garanti edilmiyor - aslında, benzersiz olmaktan çok uzak.

Küresel benzersizliği garanti ettiğini bildiğim en kısa şey Jeff Atwood'un bu blog gönderisinde yer alıyor . Bağlantılı gönderide , bir GUID'i kısaltmanın birçok yolunu tartışıyor ve sonunda Ascii85 kodlamasıyla 20 bayta indiriyor .

Ancak, 15 bayttan uzun olmayan bir çözüme kesinlikle ihtiyacınız varsa, korkarım küresel olarak benzersiz olması garanti edilmeyen bir şeyi kullanmaktan başka seçeneğiniz yok.


6

KİMLİK değerleri bir veritabanında benzersiz olmalıdır, ancak sınırlamaların farkında olmalısınız ... örneğin, toplu veri girişlerini temelde imkansız kılar ve çok sayıda kayıtla çalışıyorsanız sizi yavaşlatır.

Ayrıca bir tarih / saat değeri de kullanabilirsiniz. Tarih / saati PK olarak kullandıkları birkaç veritabanı gördüm ve süper temiz olmasa da işe yarıyor. Ekleri kontrol ederseniz, değerlerin kodda benzersiz olacağını etkili bir şekilde garanti edebilirsiniz.


6

Yerel uygulamam için bu zamana dayalı yaklaşımı kullanıyorum:

/// <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

Burada benim çözümüm eşzamanlılık için güvenli değil, saniyede 1000 GUID ve iş parçacığı için güvenli değil.

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

kod optimize edilmedi, sadece örnek!


İlginç bir çözüm olsa da, UtcNowher milisaniye için benzersiz bir tıklama değeri döndürmenin bir garantisi yoktur : açıklamalara göre çözünürlük, sistem zamanlayıcısına bağlıdır. Ek olarak, sistem saatinin geriye doğru değişmediğinden emin olmalısınız! (User13971889'un cevabı bu soruyu feed'imin en üstüne çıkardığından ve ben bu cevabı eleştirdiğim için, bu eleştiriyi burada tekrar etmem gerektiğini düşünüyorum.)
Joe Sewell

3

Uygulamanızda birkaç MILLIION çalışanı yoksa, bunu kullanarak AYNI MİLİSANİYE'de kısa benzersiz dize oluşturur, aşağıdaki işlevi kullanmayı düşünebilirsiniz.

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

Önümüzdeki 99 yıl içinde uygulamanızı kullanmanız gerekiyorsa yyyy'yi yy olarak değiştirin.
Güncelleme 20160511 : Rastgele işlevi düzelt
- Kilit nesnesi ekle
- Rastgele değişkeni RandomString işlevinden
Ref.


2
Bu harika, ancak her seferinde yeni bir Random'ı başlatmamalısınız - bunun nedeni lock, aynı Randomörneği yeniden kullanmanıza izin vermektir . Sanırım o satırı silmeyi unuttunuz!
NibblyPig

1

Yayınlanma tarihinden oldukça uzak olduğunu biliyorum ... :)

Yalnızca 9 Hexa karakteri üreten bir jeneratörüm var , örneğin: 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'in cevabına ve @ pootzko'nun yorumuna göre. Bunu kullanabilirsin. Tel üzerinden güvenlidir.

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

Biri merak ederse sonuç: Jzhw2oVozkSNa2IkyK4ilA2veya dotnetfiddle.net/VIrZ8j'de
chriszo111

1

Bazılarına dayanarak, işte URL (ve Docker) güvenli olan ve herhangi bir bilgi kaybetmeyen farklı bir kodlanmış kılavuz sağlayan çözümüm:

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

Örnek çıktılar:

BcfttHA780qMdHSxSBoZFA
_4p5srPgOE2f25T_UnoGLw
H9xR_zdfm0y-zYjdR3NOig

Bunun kabul edilen cevaptan farkı nedir?
phuzi

1

C # 'da bir longdeğer 64 bit içerir ve Base64 ile kodlanırsa, 1 dolgu dahil 12 karakter olacaktır =. Dolguyu kesersek =11 karakter olacaktır.

Buradaki çılgın fikirlerden biri, bir longdeğer oluşturmak için Unix Epoch ve bir epoch değeri için bir sayaç kombinasyonu kullanabiliriz . C # DateTimeOffset.ToUnixEpochMilliseconds'daki Unix Epoch longformattadır, ancak 8 baytın ilk 2 baytı her zaman 0'dır, çünkü aksi takdirde tarih saat değeri maksimum tarih saat değerinden daha büyük olacaktır. Bu bize bir ushortsayaç yerleştirmemiz için 2 bayt verir .

Dolayısıyla, toplamda, kimlik oluşturma sayısı milisaniye başına 65536'yı geçmediği sürece, benzersiz bir kimliğe sahip olabiliriz:

// 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)
    }

Kullanım

Guid.NewGuid().ToTinyUuid()

Geri dönmek roket bilimi değil, bu yüzden sizi bu kadar bırakacağım.


Cevap kabul edilmekten farklı değil
phuzi

0

Dizeyi yazmanız gerekmiyorsa, aşağıdakileri kullanabilirsiniz:

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

Bu, Guid'i aşağıdaki gibi 8 karakterli bir String'e dönüştürecektir:

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

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


0

İşte rastgele ve kısa benzersiz bir kimlik oluşturmak için benim küçük yöntemim. Güvenli rasgele sayı üretimi için şifreli bir rng kullanır. Dizeye ihtiyacınız olan karakterleri ekleyin 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

karakterleri kaybetmemek için (+ / -) ve rehberinizi bir url'de kullanmak istiyorsanız, bunun base32'ye dönüştürülmesi gerekir

10000000 için yinelenen anahtar yok

    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
İlginç bir çözüm olsa da, UtcNowher milisaniye için benzersiz bir tıklama değeri döndürmenin bir garantisi yoktur : açıklamalara göre çözünürlük, sistem zamanlayıcısına bağlıdır. Ek olarak, sistem saatinin geriye doğru değişmediğinden emin olmalısınız! (ur3an0'ın cevabında da bu sorunlar var.)
Joe Sewell

Kabul. Bu fakir bir adamın yaklaşımıdır ve kendi iyi kontrol edilen çevrenizin ötesinde kullanılmamalıdır.
user13971889

-1

22 karakter, url güvenli ve Guid benzersizliğini korur.

// Our url safe, base64 alphabet:
const string alphabet = "-_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

// Sanitized Guid string. Preserve the last two hex chars
var guidStr = "929F7C4D4B2644E1A122A379C02D6345";
var lastTwo = guidStr.Substring(30, 2);

string shortGuid = "";

// Iterate over the ten groups of 3 hex chars: 929 F7C 4D4 B26 44E 1A1 22A 379 C02 D63
for (var i = 0; i < 10; i++)
{
    var hex = guidStr.Substring(i*3, 3);              // Get the next 3 hex chars
    var x = Convert.ToInt32(hex, 16);                 // Convert to int
    shortGuid += $"{alphabet[x/64]}{alphabet[x%64]}"; // Lookup the two-digit base64 value
}
shortGuid += lastTwo; // Don't forget the last two

Console.WriteLine(shortGuid);

Çıktı:

yDXWhiGAfc4v6EbTK0Px45

Olumsuz oy için bir neden sunmak ister misiniz?
Didaxis

Olumsuz oy veren değil, ancak bunun kabul edilen cevaptan ne kadar farklı olduğundan emin değil, Base64'e açıkça dönüştürülmeden sadece farklı bir yol.
phuzi

Niyetim, algoritmayı gösteren bir çözüm sağlamaktı. Açıkça daha az kod satırı olan yollar var. Ancak bir şeyin nasıl çalıştığını bilmek her zaman iyidir. Bu çözüm, başka herhangi bir dile kolaylıkla taşınabilir.
Didaxis

-2

kullanabilirsiniz

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

onun 6güzel karakterler sadece 599527,143354

ve kullanıcı onu basitçe virifiye ettiğinde

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

umarım bu sana yardımcı olur


Şifrelerimi her zaman basit tutarım, hatırlaması kolay
Araç Seti

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.