.NET Короткий унікальний ідентифікатор


91

Мені потрібен унікальний ідентифікатор у .NET (не можу використовувати GUID, оскільки він занадто довгий для цього випадку).

Чи думають люди, що використаний тут алгоритм є добрим кандидатом, чи у вас є інші пропозиції?


3
Наскільки короткий? А наскільки унікальним? GUID гарантовано буде унікальним, якщо базується на апаратній адресі адаптера Ethernet; все, що створено чисто математично, ніколи не може бути доказово унікальним - просто, мабуть, унікальним (з астрономічно великою ймовірністю).
Джон

Довжиною 15 і якомога унікальнішим (можливо)
Ноель

4
var випадковий = 4; // досить добре
Kristofer

4
15 що в довжину? 15 байт? Якщо так, чому б просто не позбути байт з путівника ..?
Kristofer,

3
Вилучення байта в керівництві астрономічно підвищує ваші шанси на зіткнення ключів. Якщо ви вилучите неправильно впорядкований байт, це може напевно зіткнутися.
Кріс Марісіч

Відповіді:


100

Цей хороший - http://www.singular.co.nz/blog/archive/2007/12/20/shortguid-a-shorter-and-url-friendly-guid-in-c-sharp.aspx

а також тут GUID, схожий на YouTube

Ви можете використовувати Base64:

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

Це генерує рядок типу E1HKfn68Pkms5zsZsvKONw ==. Оскільки GUID завжди складає 128 біт, ви можете пропустити ==, який, як ви знаєте, завжди буде присутній в кінці, і це дасть вам 22 символьний рядок. Однак це не так коротко, як YouTube.


11
Коротке зауваження: якщо це потрібно для URL-адрес, той, хто їх використовує, може також захотіти дезінфікувати символи '+' та '/'
pootzko


1
Я хотів отримати унікальний ідентифікатор UnnyNet довжиною максимум 23 символи в Unity, і я застряг у своїх великих німих GUID, і ти зробив мене таким щасливим :)
nipunasudha

Цікаво, що (якщо ви працюєте з Zoom API), майже напевно вони генерують свої UUID. Вони мають 22 символи та закодовані base64.
vgel

39

Я використовую подібний підхід, як у Дона Коена, але видаляю деякі спеціальні символи:

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");

Зберігайте базову дату (яка в даному випадку - 1 січня 2016 року), починаючи з початку створення цих ідентифікаторів. Це зменшить ваші ідентифікатори.

Генерований номер: 3af3c14996e54


millisecondsдля цього DateTimeоб’єкта завжди 0
Teejay

Також видаліть останнє речення.
Teejay

1
oneliner: var uniqueId = (DateTime.Now.Ticks - новий DateTime (2016, 1, 1) .Ticks) .ToString ("x");
Sgedda,

4
Не годиться для генерації ідентифікаторів майже одночасно, як у for-loop.eg dotnetfiddle.net/L3MIgZ
Jaider

17

Простий корисний пакет. Я використовую його для генератора часових запитів ідентифікаторів.

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

https://github.com/bolorundurowb/shortid

Використовує System.Random

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

(зі сторінки github)

Якщо ви хочете контролювати тип ідентифікатора, який генерується, вказавши, чи потрібні вам цифри, спеціальні символи та довжина, зателефонуйте методу Generate та передайте три параметри: перший логічний, вказуючи, чи потрібні вам числа, другий логічний, який вказує, чи хочете ви спеціальні символи, останній номер, що вказує на вашу перевагу довжини.

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

10

Наскільки мені відомо, просто видалення частини GUID не гарантується як унікальне - насправді це далеко не унікальність.

Найкоротше, що я знаю, що гарантує глобальну унікальність, описано в цьому дописі блогу Джеффа Етвуда . У зв’язаному дописі він обговорює кілька способів скоротити GUID і, зрештою, отримує його до 20 байт за допомогою кодування Ascii85 .

Однак, якщо вам абсолютно потрібне рішення, яке не перевищує 15 байт, я боюся, що у вас немає іншого вибору, крім як використовувати те, що не гарантовано буде глобально унікальним.


6

Значення IDENTITY повинні бути унікальними в базі даних, але ви повинні знати про обмеження ... наприклад, це робить масові вставки даних в основному неможливими, що сповільнить вас, якщо ви працюєте з дуже великою кількістю записів.

Ви також можете використовувати значення дати / часу. Я бачив кілька баз даних, де вони використовують дату / час, щоб бути ПК, і хоча це не надто чисто - це працює. Якщо ви керуєте вставками, ви можете ефективно гарантувати, що значення будуть унікальними в коді.


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 в натрапив на це питання у верхній частині моєї подачі, і я критикується цей відповідь, я вважаю , я повинен повторити , що критика тут.)
Джо Sewell

3

Якщо у вашому додатку немає декількох МІЛЬЙОНІВ людей, використовуючи такі, які генерують короткий унікальний рядок в ТАКОМ же МІЛІСЕКУНДІ, ви можете подумати про використання функції нижче.

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 року.
Оновлення 20160511 : Виправити випадкову функцію
- Додати об’єкт блокування
- Перемістити випадкову величину з функції RandomString
Ref


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) і не втрачає жодної інформації:

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

Приклади результатів:

BcfttHA780qMdHSxSBoZFA
_4p5srPgOE2f25T_UnoGLw
H9xR_zdfm0y-zYjdR3NOig

1

У C # longзначення має 64 біти, які, якщо їх закодувати Base64, матимуть 12 символів, включаючи 1 заповнення =. Якщо ми обріжемо відступ =, буде 11 символів.

Однією божевільною ідеєю є те, що ми могли б використовувати комбінацію Unix Epoch та лічильника для значення однієї епохи, щоб сформувати longзначення. Епоха Unix у C # DateTimeOffset.ToUnixEpochMillisecondsмає longформат, але перші 2 байти з 8 байтів завжди дорівнюють 0, оскільки в іншому випадку значення дати буде більшим за максимальне значення часу. Отже, це дає нам 2 байти для розміщення ushortлічильника.

Отже, загалом, поки кількість генерації ідентифікаторів не перевищує 65536 за мілісекунду, ми можемо мати унікальний ідентифікатор:

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

Ось мій маленький метод для створення випадкового та короткого унікального ідентифікатора. Використовує криптографічний 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-адресі, його потрібно перетворити на 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 також мають ці проблеми.)
Джо Сьюелл

Домовились. Це підхід бідної людини, і його не слід використовувати поза власним добре контрольованим середовищем.
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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.