.NET Short Unique Identifier

I need a unique identifier in .NET (cannot use GUID as it is too long for this case).

Do people think that the algorithm used here is a good candidate or do you have any other suggestions?

154608 次浏览

这个不错,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位,你可以省略 = = ,你知道它总是 present at the end and that will give you a 22 character string. This 不像 YouTube 那么短。

IDENTITY 值在数据库中应该是唯一的,但是您应该意识到它的局限性... ... 例如,它使得批量数据插入基本上不可能,如果您使用的记录数量非常多,这将降低您的速度。

您还可以使用日期/时间值。我见过几个数据库,他们使用日期/时间作为 PK,虽然它不是非常干净-它的工作。如果控制插入,则可以有效地保证这些值在代码中是唯一的。

对于我的本地应用程序,我使用这种基于时间的方法:

/// <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;
}
Guid.NewGuid().ToString().Split('-').First()

据我所知,仅仅剥离 GUID 的一部分并不能保证是唯一的-事实上,它远非独一无二。

我所知道的保证全局唯一性的最短的东西是 Jeff Atwood 的博客文章。在链接文章中,他讨论了缩短 GUID 的多种方法,最后通过 Ascii85编码将 GUID 缩短到20字节。

但是,如果您确实需要一个不超过15字节的解决方案,那么恐怕您别无选择,只能使用不能保证全局唯一的解决方案。

这里我的解决方案,是不安全的并发,不超过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;
}

代码没有优化,只是示例。

如果您的应用程序没有几百万人,使用生成的短唯一字符串在相同的 MILLISECOND,您可以考虑使用下面的函数。

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 改成广州欢聚时代。
更新20160511 : 正确的随机函数
- 添加 Lock 对象
- 将随机变量移出随机字符串函数
裁判

我知道离发布日期还很远... :)

我有一个发电机,只产生 9 Hexa characters,例如: 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");
}
}

你可以利用

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

它的 6漂亮的字符只,599527143354

当用户简单地验证它

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

希望这个能帮到你

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

保留一个基准日期(在这种情况下是2016年1月1日) ,从那时开始生成这些 id。这会让你们的 ID 变小。

生成号码: 3af3c14996e54

I use a similar approach as Dor Cohen's but removing some special characters:

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

这将只输出字母数字字符。不能保证 UID 的长度始终相同。下面是一个例子:

vmKo0zws8k28fR4V4Hgmw
TKbhS0G2V0KqtpHOU8e6Ug
rfDi1RdO0aQHTosh9dVvw
3jhCD75fUWjQek8XRmMg
CQUg1lXIXkWG8KDFy7z6Ow
bvyxW5aj10OmKA5KMhppw
pIMK8eq5kyvLK67xtsIDg
VX4oljGWpkSQGR2OvGoOQ
NOHBjUUHv06yIc7EvotRg
iMniAuUG9kiGLwBtBQByfg

简单易用的程序包,我用它来做临时请求 ID 生成器。

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

Https://github.com/bolorundurowb/shortid

使用 System.Random

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

(来自 github 页面)

如果你想通过指定是否需要数字、特殊字符和长度来控制 id 的类型,调用 Generate 方法并传递三个参数,第一个是一个布尔值表示你是否需要数字,第二个是一个布尔值表示你是否需要特殊字符,最后一个是一个表示你的长度偏好的数字。

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

基于@dorcohen 的回答和@potzko 的评论。 你可以用这个,通过电线是安全的。

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

如果您不需要键入字符串,您可以使用以下内容:

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

This will convert the Guid to a 8 character String like this:

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

{ c5f8f7f5-8a7c-4511-b667-8ad36b446617}—— > “ something something 架 something something”

下面是我的一个小方法,用于生成一个随机的、短的、唯一的 id。使用加密环生成安全的随机数。向 chars字符串添加所需的任何字符。

using System;
using System.Security.Cryptography;


// ...


private string GenerateRandomId(int length)
{
string charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char[] outputChars = new char[length];
    

using RandomNumberGenerator rng = RandomNumberGenerator.Create();
int minIndex = 0;
int maxIndexExclusive = charset.Length;
int diff = maxIndexExclusive - minIndex;


long upperBound = uint.MaxValue / diff * diff;


byte[] randomBuffer = new byte[sizeof(int)];


for (int i = 0; i < outputChars.Length; i++)
{
// Generate a fair, random number between minIndex and maxIndex
uint randomUInt;
do
{
rng.GetBytes(randomBuffer);
randomUInt = BitConverter.ToUInt32(randomBuffer, 0);
}
while (randomUInt >= upperBound);
int charIndex = (int)(randomUInt % diff);


// Set output character based on random index
outputChars[i] = charset[charIndex];
}


return new string(outputChars);
}

这通过将一个随机整数缩放到字符集索引的范围来实现,并通过重新滚动一个新的整数来解释随机数为绝对上界的边缘情况。

这个解决方案产生公平和均匀分布的输出,测试了100万字符长的输出,没有显示出明显的偏差:

string output = GenerateRandomId(1_000_000);
var tally = output.GroupBy(c => c).OrderBy(g => g.Key).Select(g => (g.Key, g.Count())).ToArray();


int average = (int)(tally.Aggregate(new BigInteger(0), (b, t) => {b += t.Item2; return b;}, b => b) / tally.Count());
int max = tally.Max(g => g.Item2);
int min = tally.Min(g => g.Item2);


Console.WriteLine($"Avg: {average}");
Console.WriteLine($"Max: {max}");
Console.WriteLine($"Min: {min}");




foreach((char key, int count) in tally) {
Console.WriteLine($"{key}: {count}");
}

Output:

Avg: 27777
Max: 28163
Min: 27341
0: 28081
1: 27773
...
Z: 27725

基于其他一些解决方案,下面是我的解决方案,它提供了一个不同的编码指南,URL (和 Docker)是安全的,不会丢失任何信息:

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

Example outputs are:

BcfttHA780qMdHSxSBoZFA
_4p5srPgOE2f25T_UnoGLw
H9xR_zdfm0y-zYjdR3NOig

In C# a long value has 64 bits, which if encoded with Base64, there will be 12 characters, including 1 padding =. If we trim the padding =, there will be 11 characters.

这里有一个疯狂的想法,我们可以使用 Unix Epoch 和一个 Epoch 值的计数器的组合来形成一个 long值。C # DateTimeOffset.ToUnixEpochMilliseconds中的 Unix Epoch 是 long格式的,但是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('=');

为了不丢失字符(+/-) ,如果您想在 url 中使用 guid,则必须将它转换为 base32

for 10 000 000 no duplicate key

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

转换回来又不是什么难事,所以我给你留了那么多。

private static readonly object _getUniqueIdLock = new object();
public static string GetUniqueId()
{
lock(_getUniqueIdLock)
{
System.Threading.Thread.Sleep(1);
return DateTime.UtcNow.Ticks.ToString("X");
}
}

22个字符,url 安全,并保持 Guid 的唯一性。

// Our url safe, base 64 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);

产出:

yDXWhiGAfc4v6EbTK0Px45

以防只是删除连字符对任何人都有用:

Guid.NewGuid().ToString("n")

这会产生完全独一无二的32个字符的字符串:

5db4cee3bfd8436395d37fca2d48d5b3
82fac271c76148a3a0667c00a5da990d