我需要在.NET中使用一个唯一标识符(由于太长,无法使用GUID)。
人们认为这里使用的算法是一个好的选择,还是您有其他建议?
我需要在.NET中使用一个唯一标识符(由于太长,无法使用GUID)。
人们认为这里使用的算法是一个好的选择,还是您有其他建议?
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函数中移出
参考资料
这是我的解决方案,它不安全且不支持并发,每秒最多只能生成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
都会返回唯一的滴答值:根据备注,其分辨率取决于系统计时器。另外,最好确保系统时钟不会向后更改!(由于用户13971889的答案将此问题推到了我的提问列表顶部,我对该答案进行了评论,因此我认为我应该在这里重复那个评论。) - Joe Sewell 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()
将其转换回来并不是什么高深学问,所以我会留给你这个任务。
这是我生成随机且短小唯一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);
}
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}");
}
输出:
Avg: 27777
Max: 28163
Min: 27341
0: 28081
1: 27773
...
Z: 27725
我知道这篇文章的发布日期已经很久了... :)
我有一个生成器,它只能产生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");
}
}
var errorId = System.Web.HttpServerUtility.UrlTokenEncode(Guid.NewGuid().ToByteArray());
Jzhw2oVozkSNa2IkyK4ilA2
,或者您可以在 https://dotnetfiddle.net/VIrZ8j 上自行尝试。 - chriszo111long
值有64位,如果使用Base64编码,则会有12个字符,包括1个填充字符=
。如果我们去掉填充字符=
,则会有11个字符。long
值。在C#中,Unix纪元DateTimeOffset.ToUnixEpochMilliseconds
以long
格式表示,但是8字节中的前2个字节始终为0,否则日期时间值将大于最大日期时间值。因此,这给了我们2个字节来放置一个ushort
计数器。// 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('=');
如果您不需要输入字符串,可以使用以下方法:
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} --> "엸詼䔑架펊䑫ᝦ"
为了不丢失字符(+ / -),如果您想在 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;
}
private static readonly object _getUniqueIdLock = new object();
public static string GetUniqueId()
{
lock(_getUniqueIdLock)
{
System.Threading.Thread.Sleep(1);
return DateTime.UtcNow.Ticks.ToString("X");
}
}
UtcNow
每毫秒都返回唯一的滴答值:根据 备注,其分辨率取决于系统计时器。此外,您最好确保系统时钟不会向后更改!(Ur3an0 的答案也存在这些问题。) - Joe Sewell