.NET Short Unique Identifier


91

Preciso de um identificador exclusivo no .NET (não é possível usar o GUID, pois é muito longo para este caso).

As pessoas acham que o algoritmo usado aqui é um bom candidato ou você tem alguma outra sugestão?


3
Quão curto? E quão único? O GUID tem garantia de ser exclusivo se for baseado no endereço de hardware de um adaptador Ethernet; qualquer coisa produzida puramente matematicamente nunca pode ser comprovadamente única - apenas provavelmente única (com uma probabilidade astronomicamente alta).
Jon

15 de comprimento, e o mais único (provavelmente) possível
Noel

3
var random = 4; // bom o suficiente
KristoferA

3
15 o que em comprimento? 15 bytes? Se sim, por que não retirar um byte de um guid ..?
KristoferA

3
@KristoferA retirar um byte em um guid aumenta astronomicamente suas chances de colisões de chaves. Se você remover o byte errado ordenado, pode ocorrer uma colisão.
Chris Marisic

Respostas:


96

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

e também aqui o GUID do YouTube

Você pode usar Base64:

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

Isso gera uma string como E1HKfn68Pkms5zsZsvKONw ==. Como um GUID é sempre de 128 bits, você pode omitir o == que você sabe que sempre estará presente no final e que lhe dará uma string de 22 caracteres. Porém, não é tão curto quanto o YouTube.


11
Uma nota curta: se isso for necessário para URLs, quem quer que use isso pode querer limpar os caracteres '+' e '/' também
pootzko


1
Eu queria um id único de no máximo 23 caracteres para o UnnyNet no Unity, e fiquei preso com meus grandes GUIDs idiotas, e você me deixou muito feliz :)
nipunasudha

38

Eu uso uma abordagem semelhante à de Dor Cohen, mas removendo alguns caracteres especiais:

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

Isso produzirá apenas caracteres alfanuméricos. Não é garantido que os UIDs tenham sempre o mesmo comprimento. Aqui está um exemplo de execução:

vmKo0zws8k28fR4V4Hgmw 
TKbhS0G2V0KqtpHOU8e6Ug 
rfDi1RdO0aQHTosh9dVvw
3jhCD75fUWjQek8XRmMg 
CQUg1lXIXkWG8KDFy7z6Ow 
bvyxW5aj10OmKA5KMhppw
pIMK8eq5kyvLK67xtsIDg
VX4oljGWpkSQGR2OvGoOQ 
NOHBjUUHv06yIc7EvotRg
iMniAuUG9kiGLwBtBQByfg

6
Você perderá algumas propriedades garantidas por GUIDs descartando informações como esta. Eu recomendaria substituir os caracteres com os quais você não se sente confortável por caracteres diferentes, preservando a bijeção entre os GUIDs e seu formato de serialização.
Lukáš Lánský

3
Este é um bom para usar se você NÃO quiser converter de volta para um GUID, mas só precisa de caracteres aleatórios para outra coisa ... por exemplo, rastreamento de workers em vários threads ou Prefixo de registro para objetos em thread, etc.
Piotr Kula

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

Mantenha uma data de referência (que neste caso é 1º de janeiro de 2016) a partir de quando você começará a gerar esses ids. Isso tornará seus ids menores.

Número gerado: 3af3c14996e54


millisecondsé sempre 0 para esse DateTimeobjeto
Teejay

Remova também a última frase.
Teejay

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

4
Não é bom para geração de ids quase ao mesmo tempo, como em for-loop.eg dotnetfiddle.net/L3MIgZ
Jaider

16

Pacote de uso simples. Eu o uso para gerador de ID de solicitação temporal.

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

https://github.com/bolorundurowb/shortid

Usos System.Random

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

(da página do github)

Se você deseja controlar o tipo de id gerado especificando se deseja números, caracteres especiais e o comprimento, chame o método Generate e passe três parâmetros, o primeiro um booleano informando se deseja números, o segundo um booleano informando se deseja caracteres especiais, o último um número que indica sua preferência de comprimento.

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

10

Pelo que eu sei, apenas retirar uma parte de um GUID não é garantido como sendo único - na verdade, está longe de ser único.

A coisa mais curta que eu sei que garante exclusividade global é apresentada nesta postagem do blog de Jeff Atwood . Na postagem vinculada, ele discute várias maneiras de encurtar um GUID e, no final, reduz para 20 bytes por meio da codificação Ascii85 .

No entanto, se você precisa absolutamente de uma solução que não seja maior que 15 bytes, infelizmente você não tem outra escolha a não ser usar algo que não é garantido como único globalmente.


6

Os valores de IDENTITY devem ser únicos em um banco de dados, mas você deve estar ciente das limitações ... por exemplo, torna a inserção de dados em massa basicamente impossível, o que o tornará mais lento se estiver trabalhando com um número muito grande de registros.

Você também pode usar um valor de data / hora. Eu vi vários bancos de dados onde eles usam a data / hora para ser o PK e, embora não seja muito limpo - funciona. Se você controlar as inserções, poderá garantir com eficácia que os valores serão exclusivos no código.


6

Para meu aplicativo local, estou usando esta abordagem baseada no 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

aqui minha solução, não é segura para simultaneidade, não mais de 1000 GUIDs por segundos 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;
    }

código não otimizado, apenas uma amostra !.


Embora seja uma solução interessante, não há garantia de que UtcNowretornará um valor de escala exclusivo para cada milissegundo: de acordo com as observações , a resolução depende do cronômetro do sistema. Além disso, é melhor garantir que o relógio do sistema não mude para trás! (Como a resposta do usuário 13971889 colocou essa pergunta no topo do meu feed e eu critiquei essa resposta, acho que devo repetir essa crítica aqui.)
Joe Sewell

3

Se seu aplicativo não tem alguns MILHÕES de pessoas, usando essa string única e curta no MESMO MILISEGUNDO, você pode pensar em usar a função abaixo.

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

altere yyyy para yy se você só precisar usar seu aplicativo nos próximos 99 anos.
Atualização 20160511 : Função aleatória correta
- Adicionar objeto de bloqueio
- Mover variável aleatória para fora da função RandomString
Ref


1
Isso é ótimo, embora você não deva inicializar um novo Random todas as vezes - a razão para isso locké permitir que você reutilize a mesma Randominstância. Acho que você se esqueceu de deletar essa linha!
NibblyPig

1

Eu sei que está muito longe da data postada ... :)

Eu tenho um gerador que produz apenas 9 caracteres hexa, por exemplo: 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

Com base na resposta de @ dorcohen e no comentário de @pootzko. Você pode usar isso. É seguro durante o fio.

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

Resultado, se alguém se perguntar: Jzhw2oVozkSNa2IkyK4ilA2ou tente você mesmo em dotnetfiddle.net/VIrZ8j
chriszo111

1

Com base em alguns outros, aqui está minha solução que fornece um guid codificado diferente que é seguro para URL (e Docker) e não perde nenhuma informação:

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

Os exemplos de saída são:

BcfttHA780qMdHSxSBoZFA
_4p5srPgOE2f25T_UnoGLw
H9xR_zdfm0y-zYjdR3NOig

1

Em C # um longvalor possui 64 bits, que se codificado com Base64, haverá 12 caracteres, incluindo 1 preenchimento =. Se cortarmos o preenchimento =, haverá 11 caracteres.

Uma ideia maluca aqui é que poderíamos usar uma combinação de Unix Epoch e um contador para um valor de época para formar um longvalor. O Unix Epoch em C # DateTimeOffset.ToUnixEpochMillisecondsestá no longformato, mas os primeiros 2 bytes dos 8 bytes são sempre 0, caso contrário, o valor de data e hora será maior que o valor máximo de data e hora. Isso nos dá 2 bytes para colocar um ushortcontador.

Portanto, no total, desde que o número de geração de IDs não exceda 65536 por milissegundo, podemos ter um ID único:

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

Uso

Guid.NewGuid().ToTinyUuid()

Não é ciência do foguete converter de volta, então vou deixar você por isso.


0

Se você não precisa digitar a string, pode usar o seguinte:

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

Isso converterá o Guid em uma string de 8 caracteres como esta:

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

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


0

Este é o meu pequeno método para gerar um id único aleatório e curto. Usa um rng criptográfico para geração segura de números aleatórios. Adicione quaisquer caracteres que você precisa para a charsstring.

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

para não perder caracteres (+/-) e se quiser usar seu guid em uma url, deve ser transformado em base32

para 10.000.000 nenhuma chave duplicada

    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
Embora seja uma solução interessante, não há garantia de que UtcNowretornará um valor de escala exclusivo para cada milissegundo: de acordo com as observações , a resolução depende do cronômetro do sistema. Além disso, é melhor garantir que o relógio do sistema não mude para trás! (a resposta de ur3an0 também tem esses problemas.)
Joe Sewell

Acordado. Esta é uma abordagem de homem pobre e não deve ser usada fora de seu próprio ambiente bem controlado.
user13971889

-2

você pode usar

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

seus 6única agradáveis personagens, 599527,143354

e quando o usuário virifica simplesmente

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

espero que isso ajude você


Sempre mantenho minhas senhas simples, fáceis de lembrar
Toolkit


-6

Eu uso Guid.NewGuid().ToString().Split('-')[0], ele obtém o primeiro item do array separado por '-'. É o suficiente para representar uma chave única.


3
Isso está incorreto. Veja este link .
Kasey Speakman
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.