Identificatore univoco breve .NET


91

Ho bisogno di un identificatore univoco in .NET (non posso usare GUID perché è troppo lungo per questo caso).

Le persone pensano che l'algoritmo utilizzato qui sia un buon candidato o hai altri suggerimenti?


3
Quanto breve? E quanto è unico? GUID è garantito per essere univoco se basato sull'indirizzo hardware di un adattatore ethernet; qualsiasi cosa prodotta puramente matematicamente non può mai essere provabilmente unica - solo probabilmente unica (con una probabilità astronomicamente alta).
Jon

15 di lunghezza e il più unico (probabilmente) possibile
Noel

3
var random = 4; // abbastanza buono
KristoferA

3
15 che lunghezza? 15 byte? Se è così, perché non rimuovere semplicemente un byte da una guida?
KristoferA

3
@KristoferA togliere un byte in una guida aumenta astronomicamente le tue possibilità di collisioni chiave. Se si elimina il byte ordinato sbagliato, potrebbe sicuramente entrare in collisione.
Chris Marisic

Risposte:


96

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

e anche qui GUID simile a YouTube

Puoi usare Base64:

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

Questo genera una stringa come E1HKfn68Pkms5zsZsvKONw ==. Poiché un GUID è sempre a 128 bit, puoi omettere il == che sai sarà sempre presente alla fine e che ti darà una stringa di 22 caratteri. Tuttavia, non è così breve come YouTube.


11
Una breve nota: se questo è necessario per gli URL, chiunque lo usi potrebbe voler disinfettare anche i caratteri "+" e "/"
pootzko


1
Volevo un ID univoco di massimo 23 caratteri per UnnyNet in Unity, ed ero bloccato con i miei grandi e stupidi GUID, e mi hai reso così felice :)
nipunasudha

38

Uso un approccio simile a quello di Dor Cohen ma rimuovendo alcuni caratteri speciali:

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

Questo produrrà solo caratteri alfanumerici. Non è garantito che gli UID abbiano sempre la stessa lunghezza. Ecco un esempio di esecuzione:

vmKo0zws8k28fR4V4Hgmw 
TKbhS0G2V0KqtpHOU8e6Ug 
rfDi1RdO0aQHTosh9dVvw
3jhCD75fUWjQek8XRmMg 
CQUg1lXIXkWG8KDFy7z6Ow 
bvyxW5aj10OmKA5KMhppw
pIMK8eq5kyvLK67xtsIDg
VX4oljGWpkSQGR2OvGoOQ 
NOHBjUUHv06yIc7EvotRg
iMniAuUG9kiGLwBtBQByfg

6
Perderai alcune proprietà garantite dai GUID buttando via informazioni come questa. Consiglierei di sostituire i caratteri con cui non ti senti a tuo agio con caratteri diversi preservando la biiezione tra i GUID e il loro formato di serializzazione.
Lukáš Lánský

3
Questo è bello da usare se NON vuoi riconvertirlo in un GUID ma hai solo bisogno di caratteri casuali per qualcos'altro .. per esempio tracciare i lavoratori in più thread, o il prefisso di registrazione per oggetti con thread, ecc.
Piotr Kula

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

Mantieni una data di riferimento (che in questo caso è il 1 ° gennaio 2016) da quando inizierai a generare questi ID. Questo renderà i tuoi ID più piccoli.

Numero generato: 3af3c14996e54


millisecondsè sempre 0 per DateTimequell'oggetto
Teejay

Rimuovi anche l'ultima frase.
Teejay

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

4
Non va bene per la generazione di ID quasi contemporaneamente come in for-loop.eg dotnetfiddle.net/L3MIgZ
Jaider

16

Pacchetto utilizzabile semplice. Lo uso per il generatore di id di richiesta temporale.

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

https://github.com/bolorundurowb/shortid

Utilizza System.Random

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

(dalla pagina GitHub)

Se vuoi controllare il tipo di id generato specificando se vuoi numeri, caratteri speciali e la lunghezza, chiama il metodo Generate e passa tre parametri, il primo un booleano che indica se vuoi numeri, il secondo un booleano che indica se vuoi caratteri speciali, l'ultimo un numero che indica la tua preferenza di lunghezza.

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

10

Per quanto ne so, la semplice rimozione di una parte di un GUID non è garantita per essere unica , anzi, è tutt'altro che unica.

La cosa più breve che io sappia che garantisce l'unicità globale è descritta in questo post del blog di Jeff Atwood . Nel post collegato, discute diversi modi per abbreviare un GUID e alla fine lo riduce a 20 byte tramite la codifica Ascii85 .

Tuttavia, se hai assolutamente bisogno di una soluzione che non sia più lunga di 15 byte, temo che tu non abbia altra scelta che usare qualcosa che non è garantito essere unico a livello globale.


6

I valori IDENTITY dovrebbero essere univoci in un database, ma dovresti essere consapevole dei limiti ... ad esempio, rende praticamente impossibili gli inserimenti di dati in blocco, il che ti rallenterà se stai lavorando con un numero molto elevato di record.

Potresti anche essere in grado di utilizzare un valore di data / ora. Ho visto diversi database in cui usano la data / ora come PK e, sebbene non sia super pulito, funziona. Se controlli gli inserti, puoi effettivamente garantire che i valori saranno univoci nel codice.


6

Per la mia app locale sto usando questo approccio basato sul tempo:

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

qui la mia soluzione, non è sicura per la concorrenza, non più di 1000 GUID al secondo e thread safe.

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

codice non ottimizzato, solo campione !.


Sebbene sia una soluzione interessante, non esiste alcuna garanzia che UtcNowrestituisca un valore di tick univoco per ogni millisecondo: secondo le osservazioni , la risoluzione dipende dal timer di sistema. Inoltre, faresti meglio ad assicurarti che l'orologio di sistema non cambi all'indietro! (Dal momento che la risposta di user13971889 ha spostato questa domanda in cima al mio feed e ho criticato quella risposta, immagino che dovrei ripetere quella critica qui.)
Joe Sewell

3

Se la tua app non ha poche MILIONI di persone, utilizzando quella stringa univoca che genera lo STESSO MILLISECONDO, puoi pensare di utilizzare la funzione di seguito.

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

cambia yyyy in yy se devi solo usare la tua app nei prossimi 99 anni.
Aggiornamento 20160511 : funzione casuale corretta
- Aggiungi oggetto Lock
- Sposta la variabile casuale fuori dalla funzione RandomString
Ref


1
Questo è ottimo anche se non dovresti inizializzare un nuovo Random ogni volta: il motivo lockè che ti consente di riutilizzare la stessa Randomistanza. Penso che ti sei dimenticato di cancellare quella riga!
NibblyPig

1

So che è abbastanza lontano dalla data di pubblicazione ... :)

Ho un generatore che produce solo 9 caratteri Hexa , ad esempio: 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

Basato sulla risposta di @ dorcohen e sul commento di @ pootzko. Puoi usarlo. È sicuro sul filo.

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

Risultato se qualcuno se lo chiede: Jzhw2oVozkSNa2IkyK4ilA2o prova te stesso su dotnetfiddle.net/VIrZ8j
chriszo111

1

Sulla base di alcuni altri, ecco la mia soluzione che fornisce una guida codificata diversa che è sicura per l'URL (e Docker) e non perde alcuna informazione:

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

Gli output di esempio sono:

BcfttHA780qMdHSxSBoZFA
_4p5srPgOE2f25T_UnoGLw
H9xR_zdfm0y-zYjdR3NOig

1

In C # un longvalore ha 64 bit, che se codificati con Base64, ci saranno 12 caratteri, incluso 1 riempimento =. Se tagliamo l'imbottitura =, ci saranno 11 caratteri.

Un'idea folle qui è che potremmo usare una combinazione di Unix Epoch e un contatore per un valore di epoch per formare un longvalore. L'epoca Unix in C # DateTimeOffset.ToUnixEpochMillisecondsè in longformato, ma i primi 2 byte degli 8 byte sono sempre 0, perché altrimenti il ​​valore di data e ora sarà maggiore del valore massimo di data e ora. Quindi questo ci dà 2 byte per inserire un ushortcontatore.

Quindi, in totale, fintanto che il numero di generazione di ID non supera 65536 per millisecondo, possiamo avere un ID univoco:

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

Utilizzo

Guid.NewGuid().ToTinyUuid()

Non è scienza missilistica riconvertirsi, quindi ti lascio così tanto.


0

Se non è necessario digitare la stringa, è possibile utilizzare quanto segue:

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

Questo convertirà il Guid in una stringa di 8 caratteri come questa:

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

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


0

Ecco il mio piccolo metodo per generare un ID univoco casuale e breve. Utilizza un rng crittografico per la generazione sicura di numeri casuali. Aggiungi tutti i caratteri di cui hai bisogno alla charsstringa.

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

per non perdere caratteri (+ / -) e se vuoi usare il tuo guid in un url, devi trasformarlo in base32

per 10000000 nessuna chiave duplicata

    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
Sebbene sia una soluzione interessante, non esiste alcuna garanzia che UtcNowrestituisca un valore di tick univoco per ogni millisecondo: secondo le osservazioni , la risoluzione dipende dal timer di sistema. Inoltre, faresti meglio ad assicurarti che l'orologio di sistema non cambi all'indietro! (Anche la risposta di ur3an0 ha questi problemi.)
Joe Sewell,

Concordato. Questo è un approccio da uomo povero e non dovrebbe essere usato al di fuori del proprio ambiente ben controllato.
user13971889

-2

Puoi usare

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

solo i suoi 6simpatici personaggi 599527,143354

e quando l'utente lo virifica semplicemente

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

spero che questo ti aiuti


Mantengo sempre le mie password semplici, facili da ricordare
Toolkit


-6

Io uso Guid.NewGuid().ToString().Split('-')[0], ottiene il primo elemento dall'array separato da "-". È sufficiente per rappresentare una chiave unica.


3
Questo non è corretto. Vedi questo collegamento .
Kasey Speakman
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.