.NETの短い一意の識別子


91

.NETで一意の識別子が必要です(この場合、GUIDは長すぎるため使用できません)。

ここで使用されているアルゴリズムは良い候補だと人々は思いますか、それとも他に提案がありますか?


3
どれくらい短い?そして、どのようにユニークですか?イーサネットアダプタのハードウェアアドレスに基づいている場合、GUIDは一意であることが保証されています。純粋に数学的に生成されたものは、証明できるほど一意になることは決してありません-おそらく一意である(天文学的に高い確率で)。
Jon

15の長さで、可能な限りユニークな(おそらく)
Noel

3
var random = 4; //十分
KristoferA

3
15 の長さで?15バイト?もしそうなら、なぜガイドからバイトを取り除くのではないのですか?
KristoferA

3
@KristoferAがGUIDでバイトを取り除くと、天文学的にキーの衝突の可能性が高まります。間違った順序のバイトを取り除くと、確実に衝突する可能性があります。
Chris Marisic 16

回答:


96

これは良いものです-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に必要な場合、これを使用する人は '+'と '/'文字も
サニタイズし


1
UnityでUnnyNetの最大23文字の一意のIDが欲しかったのですが、私は大きなダムGUIDに
悩まさ

38

私は、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とそのシリアル化形式の間の全単射を維持しながら、使いにくい文字を別の文字に置き換えることをお勧めします。
ルカシュLánský

3
これは、GUIDに変換し直したくないが、何か別のランダムな文字が必要な場合に使用すると便利です。たとえば、マルチスレッドでのワーカーの追跡や、スレッドオブジェクトのLogging Prefixなど
Piotr Kula

22
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

4
ほとんどfor-loop.egのように同時にIDの生成のためには良いんdotnetfiddle.net/L3MIgZ
Jaider

16

シンプルで使いやすいパッケージ。一時的なリクエストIDジェネレーターに使用します。

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

https://github.com/bolorundurowb/shortid

用途 System.Random

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

(githubページから)

数値、特殊文字、長さのいずれを必要とするかを指定して生成されるIDのタイプを制御する場合は、Generateメソッドを呼び出して3つのパラメーターを渡します。1つは数値が必要かどうかを示すブール値、2つ目は必要かどうかを示すブール値です特殊文字、最後の数字は好みの長さを示します。

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

10

私の知る限り、GUIDの一部を削除するだけでは一意であるとは限りません。実際、であるとはほど遠いものです。

ジェフ・アトウッドによるこのブログ記事では、グローバルな一意性を保証することがわかっている最も短いものを取り上げています。リンクされた投稿で、彼はGUIDを短縮するための複数の方法について説明し、最終的にはAscii85エンコーディングを介してGUIDを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

ここで私の解決策は、同時実行に対して安全ではなく、1秒あたり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

アプリに数百万人がいない場合、同じミリ秒で短い一意の文字列を生成することで、以下の関数の使用を検討できます。

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機能のうち、確率変数を移動-
文献を


1
毎回新しいRandomを初期化するべきではありませんが、これは素晴らしいことです。これはlock、同じRandomインスタンスを再利用できるようにするためです。その行を削除するのを忘れたようです!
NibblyPig

1

私はそれが投稿された日付からかなり遠いことを知っています... :)

9つのヘキサ文字しか生成しないジェネレーターがあります。例: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文字になります。

ここで1つの奇妙なアイデアは、Unix Epochと1つのエポック値のカウンターの組み合わせを使用して値を形成できるというlongことです。C#のUnixエポックDateTimeOffset.ToUnixEpochMillisecondslong形式ですが、8バイトの最初の2バイトは常に0です。それ以外の場合、日時の値は最大日時の値よりも大きくなります。つまり、2バイトを配置するushortカウンターためのられます。

したがって、全体として、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素敵なキャラクターだけ599527143354

そして、ユーザーがそれを単純に仮想化するとき

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

これがあなたを助けることを願っています


パスワードは常にシンプルで覚えやすいものにしておく
ツールキット


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.