生成唯一值的方法很多,下面就不同环境下生成的唯一标识方法一一介绍,作为工作中的一次总结,有兴趣的可以自行测试:
一、在 .NET 中生成
1、直接用.NET Framework 提供的 Guid() 函数,此种方法使用非常广泛。GUID(全局统一标识符)是指在一台机器上生成的数字,它保证对在同一时空中的任何两台计算机都不会生成重复的 GUID 值(即保证所有机器都是唯一的)。关于GUID的介绍在此不作具体熬述,想深入了解可以自行查阅MSDN。代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 namespace ConsoleApplication1 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 string _guid = GetGuid(); 12 Console.WriteLine("唯一码:{0}\t长度为:{1}\n去掉连接符:{2}", _guid, _guid.Length, _guid.Replace("-", "")); 13 string uniqueIdString = GuidTo16String(); 14 Console.WriteLine("唯一码:{0}\t长度为:{1}", uniqueIdString, uniqueIdString.Length); 15 long uniqueIdLong = GuidToLongID(); 16 Console.WriteLine("唯一码:{0}\t长度为:{1}", uniqueIdLong, uniqueIdLong.ToString().Length); 17 } 18 /// <summary> 19 /// 由连字符分隔的32位数字 20 /// </summary> 21 /// <returns></returns> 22 private static string GetGuid() 23 { 24 System.Guid guid = new Guid(); 25 guid = Guid.NewGuid(); 26 return guid.ToString(); 27 } 28 /// <summary> 29 /// 根据GUID获取16位的唯一字符串 30 /// </summary> 31 /// <param name=\"guid\"></param> 32 /// <returns></returns> 33 public static string GuidTo16String() 34 { 35 long i = 1; 36 foreach (byte b in Guid.NewGuid().ToByteArray()) 37 i *= ((int)b + 1); 38 return string.Format("{0:x}", i - DateTime.Now.Ticks); 39 } 40 /// <summary> 41 /// 根据GUID获取19位的唯一数字序列 42 /// </summary> 43 /// <returns></returns> 44 public static long GuidToLongID() 45 { 46 byte[] buffer = Guid.NewGuid().ToByteArray(); 47 return BitConverter.ToInt64(buffer, 0); 48 } 49 } 50 }
2、用 DateTime.Now.ToString("yyyyMMddHHmmssms") 和 .NET Framework 提供的 RNGCryptoServiceProvider() 结合生成,代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 namespace ConsoleApplication1 7 { 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 string uniqueNum = GenerateOrderNumber(); 13 Console.WriteLine("唯一码:{0}\t 长度为:{1}", uniqueNum, uniqueNum.Length); 14 //测试是否会生成重复 15 Console.WriteLine("时间+RNGCryptoServiceProvider()结合生成的唯一值,如下:"); 16 string _tempNum = string.Empty; 17 for (int i = 0; i < 1000; i++) 18 { 19 string uNum = GenerateOrderNumber(); 20 Console.WriteLine(uNum); 21 if (string.Equals(uNum, _tempNum)) 22 { 23 Console.WriteLine("上值存在重复,按Enter键继续"); 24 Console.ReadKey(); 25 } 26 //Sleep当前线程,是为了延时,从而不产生重复值。可以把它注释掉测试看 27 Thread.Sleep(300); 28 _tempNum = uNum; 29 } 30 } 31 /// <summary> 32 /// 唯一订单号生成 33 /// </summary> 34 /// <returns></returns> 35 public static string GenerateOrderNumber() 36 { 37 string strDateTimeNumber = DateTime.Now.ToString("yyyyMMddHHmmssms"); 38 string strRandomResult = NextRandom(1000, 1).ToString(); 39 return strDateTimeNumber + strRandomResult; 40 } 41 /// <summary> 42 /// 参考:msdn上的RNGCryptoServiceProvider例子 43 /// </summary> 44 /// <param name="numSeeds"></param> 45 /// <param name="length"></param> 46 /// <returns></returns> 47 private static int NextRandom(int numSeeds, int length) 48 { 49 // Create a byte array to hold the random value. 50 byte[] randomNumber = new byte[length]; 51 // Create a new instance of the RNGCryptoServiceProvider. 52 System.Security.Cryptography.RNGCryptoServiceProvider rng = new System.Security.Cryptography.RNGCryptoServiceProvider(); 53 // Fill the array with a random value. 54 rng.GetBytes(randomNumber); 55 // Convert the byte to an uint value to make the modulus operation easier. 56 uint randomResult = 0x0; 57 for (int i = 0; i < length; i++) 58 { 59 randomResult |= ((uint)randomNumber[i] << ((length - 1 - i) * 8)); 60 } 61 return (int)(randomResult % numSeeds) + 1; 62 } 63 } 64 }
3、用 [0-9A-Z] + Guid.NewGuid() 结合生成特定位数的唯一字符串,代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 namespace ConsoleApplication1 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 string uniqueText = GenerateUniqueText(8); 12 Console.WriteLine("唯一码:{0}\t 长度为:{1}", uniqueText, uniqueText.Length); 13 //测试是否会生成重复 14 Console.WriteLine("由[0-9A-Z] + NewGuid() 结合生成的唯一值,如下:"); 15 IList<string> list = new List<string>(); 16 for (int i = 1; i <= 1000; i++) 17 { 18 string _uT = GenerateUniqueText(8); 19 Console.WriteLine("{0}\t{1}", list.Count, _uT); 20 if (list.Contains(_uT)) 21 { 22 Console.WriteLine("{0}值存在重复", _uT); 23 Console.ReadKey(); 24 } 25 list.Add(_uT); 26 //if (i % 200 == 0) 27 //{ 28 //Console.WriteLine("没有重复,按Enter键往下看"); 29 //Console.ReadKey(); 30 //} 31 } 32 list.Clear(); 33 } 34 35 /// <summary> 36 /// 生成特定位数的唯一字符串 37 /// </summary> 38 /// <param name="num">特定位数</param> 39 /// <returns></returns> 40 public static string GenerateUniqueText(int num) 41 { 42 string randomResult = string.Empty; 43 string readyStr = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 44 char[] rtn = new char[num]; 45 Guid gid = Guid.NewGuid(); 46 var ba = gid.ToByteArray(); 47 for (var i = 0; i < num; i++) 48 { 49 rtn[i] = readyStr[((ba[i] + ba[num + i]) % 35)]; 50 } 51 foreach (char r in rtn) 52 { 53 randomResult += r; 54 } 55 return randomResult; 56 } 57 } 58 }
4、用单例模式实现,由[0-9a-z]组合生成的唯一值,此文不讨论单例模式的多种实现方式与性能问题,随便弄一种方式实现,代码如下:
Demo结构如图:
Program.cs 程序:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Collections; 6 using System.Xml; 7 namespace ConsoleApplication4 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 CreateID createID = CreateID.GetInstance(); 14 //测试是否会生成重复 15 Console.WriteLine("单例模式实现,由[0-9a-z]组合生成的唯一值,如下:"); 16 IList<string> list = new List<string>(); 17 for (int i = 1; i <= 1000000000; i++) 18 { 19 string strUniqueNum = createID.CreateUniqueID(); 20 Console.WriteLine("{0}\t{1}", list.Count, strUniqueNum); 21 if (list.Contains(strUniqueNum)) 22 { 23 Console.WriteLine("{0}值存在重复", strUniqueNum); 24 Console.ReadKey(); 25 } 26 list.Add(strUniqueNum); 27 if (i % 200 == 0) 28 { 29 Console.WriteLine("没有重复,按Enter键往下看"); 30 Console.ReadKey(); 31 } 32 } 33 list.Clear(); 34 } 35 } 36 /// <summary> 37 /// 单例模式实现 38 /// 唯一值由[0-9a-z]组合而成,且生成的每个ID不能重复 39 /// </summary> 40 public class CreateID 41 { 42 private static CreateID _instance; 43 private static readonly object syncRoot = new object(); 44 private EHashtable hashtable = new EHashtable(); 45 private string _strXMLURL = string.Empty; 46 private CreateID() 47 { 48 hashtable.Add("0", "0"); 49 hashtable.Add("1", "1"); 50 hashtable.Add("2", "2"); 51 hashtable.Add("3", "3"); 52 hashtable.Add("4", "4"); 53 hashtable.Add("5", "5"); 54 hashtable.Add("6", "6"); 55 hashtable.Add("7", "7"); 56 hashtable.Add("8", "8"); 57 hashtable.Add("9", "9"); 58 hashtable.Add("10", "a"); 59 hashtable.Add("11", "b"); 60 hashtable.Add("12", "c"); 61 hashtable.Add("13", "d"); 62 hashtable.Add("14", "e"); 63 hashtable.Add("15", "f"); 64 hashtable.Add("16", "g"); 65 hashtable.Add("17", "h"); 66 hashtable.Add("18", "i"); 67 hashtable.Add("19", "j"); 68 hashtable.Add("20", "k"); 69 hashtable.Add("21", "l"); 70 hashtable.Add("22", "m"); 71 hashtable.Add("23", "n"); 72 hashtable.Add("24", "o"); 73 hashtable.Add("25", "p"); 74 hashtable.Add("26", "q"); 75 hashtable.Add("27", "r"); 76 hashtable.Add("28", "s"); 77 hashtable.Add("29", "t"); 78 hashtable.Add("30", "u"); 79 hashtable.Add("31", "v"); 80 hashtable.Add("32", "w"); 81 hashtable.Add("33", "x"); 82 hashtable.Add("34", "y"); 83 hashtable.Add("35", "z"); 84 _strXMLURL = System.IO.Path.GetFullPath(@"..\..\") + "XMLs\\record.xml"; 85 86 } 87 public static CreateID GetInstance() 88 { 89 if (_instance == null) 90 { 91 lock (syncRoot) 92 { 93 if (_instance == null) 94 { 95 _instance = new CreateID(); 96 } 97 } 98 } 99 return _instance; 100 } 101 /// <summary> 102 /// 创建UniqueID 103 /// </summary> 104 /// <returns>UniqueID</returns> 105 public string CreateUniqueID() 106 { 107 long _uniqueid = GetGuidFromXml(); 108 return Convert10To36(_uniqueid); 109 } 110 /// <summary> 111 /// 获取UniqueID总记录,即获取得到的这个ID是第几个ID 112 /// 更新UniqueID使用的个数,用于下次使用 113 /// </summary> 114 /// <returns></returns> 115 private long GetGuidFromXml() 116 { 117 long record = 0; 118 XmlDocument xmldoc = new XmlDocument(); 119 xmldoc.Load(_strXMLURL); 120 XmlElement rootNode = xmldoc.DocumentElement; 121 //此次的个数值 122 record = Convert.ToInt64(rootNode["record"].InnerText); 123 //此次的个数值+1 == 下次的个数值 124 rootNode["record"].InnerText = Convert.ToString(record + 1); 125 xmldoc.Save(_strXMLURL); 126 return record; 127 } 128 /// <summary> 129 /// 10进制转36进制 130 /// </summary> 131 /// <param name="intNum10">10进制数</param> 132 /// <returns></returns> 133 private string Convert10To36(long intNum10) 134 { 135 string strNum36 = string.Empty; 136 long result = intNum10 / 36; 137 long remain = intNum10 % 36; 138 if (hashtable.ContainsKey(remain.ToString())) 139 strNum36 = hashtable[remain.ToString()].ToString() + strNum36; 140 intNum10 = result; 141 while (intNum10 / 36 != 0) 142 { 143 result = intNum10 / 36; 144 remain = intNum10 % 36; 145 if (hashtable.ContainsKey(remain.ToString())) 146 strNum36 = hashtable[remain.ToString()].ToString() + strNum36; 147 intNum10 = result; 148 } 149 if (intNum10 > 0 && intNum10 < 36) 150 { 151 if (hashtable.ContainsKey(intNum10.ToString())) 152 strNum36 = hashtable[intNum10.ToString()].ToString() + strNum36; 153 } 154 return strNum36; 155 } 156 } 157 /// <summary> 158 /// Summary description for EHashTable 159 /// </summary> 160 public class EHashtable : Hashtable 161 { 162 private ArrayList list = new ArrayList(); 163 public override void Add(object key, object value) 164 { 165 base.Add(key, value); 166 list.Add(key); 167 } 168 public override void Clear() 169 { 170 base.Clear(); 171 list.Clear(); 172 } 173 public override void Remove(object key) 174 { 175 base.Remove(key); 176 list.Remove(key); 177 } 178 public override ICollection Keys 179 { 180 get 181 { 182 return list; 183 } 184 } 185 } 186 }
XML:
1 <?xml version="1.0" encoding="utf-8"?> 2 <root> 3 <record id="record">1</record> 4 </root>
二、在JS中生成GUID,类似.NET中的 Guid.NewGuid(),代码如下:
1 function newGuid() { //方法一: 2 var guid = ""; 3 var n = (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); 4 for (var i = 1; i <= 8; i++) { 5 guid += n; 6 } 7 return guid; 8 } 9 function newGuid() { //方法二: 10 var guid = ""; 11 for (var i = 1; i <= 32; i++) { 12 var n = Math.floor(Math.random() * 16.0).toString(16); 13 guid += n; 14 if ((i == 8) || (i == 12) || (i == 16) || (i == 20)) 15 guid += "-"; 16 } 17 return guid; 18 }
三、在SQL存储过程生成GUID,代码如下:
1 -- ============================================= 2 -- Author: JBen 3 -- Create date: 2012-06-05 4 -- Description: 生成唯一标识ID,公共存储过程,可设置在别的存储过程调用此存储过程传不同的前缀 5 -- ============================================= 6 ALTER PROCEDURE [dbo].[pro_CreateGuid] 7 @Prefix NVARCHAR(10), 8 @outputV_guid NVARCHAR(40) OUTPUT 9 AS 10 BEGIN 11 -- SET NOCOUNT ON added to prevent extra result sets from 12 -- interfering with SELECT statements. 13 SET NOCOUNT ON; 14 -- Insert statements for procedure here 15 SET @outputV_guid = @Prefix + REPLACE(CAST(NEWID() AS VARCHAR(36)),'-','') 16 END
5. Twitter的snowflake算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。
C#代码如下:
/// <summary> /// From: https://github.com/twitter/snowflake /// An object that generates IDs. /// This is broken into a separate class in case /// we ever want to support multiple worker threads /// per process /// </summary> public class IdWorker { private long workerId; private long datacenterId; private long sequence = 0L; private static long twepoch = 1288834974657L; private static long workerIdBits = 5L; private static long datacenterIdBits = 5L; private static long maxWorkerId = -1L ^ (-1L << (int)workerIdBits); private static long maxDatacenterId = -1L ^ (-1L << (int)datacenterIdBits); private static long sequenceBits = 12L; private long workerIdShift = sequenceBits; private long datacenterIdShift = sequenceBits + workerIdBits; private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; private long sequenceMask = -1L ^ (-1L << (int)sequenceBits); private long lastTimestamp = -1L; private static object syncRoot = new object(); public IdWorker(long workerId, long datacenterId) { // sanity check for workerId if (workerId > maxWorkerId || workerId < 0) { throw new ArgumentException(string.Format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new ArgumentException(string.Format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; } public long nextId() { lock (syncRoot) { long timestamp = timeGen(); if (timestamp < lastTimestamp) { throw new ApplicationException(string.Format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L; } lastTimestamp = timestamp; return ((timestamp - twepoch) << (int)timestampLeftShift) | (datacenterId << (int)datacenterIdShift) | (workerId << (int)workerIdShift) | sequence; } } protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } protected long timeGen() { return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds; } }
测试代码如下:
private static void TestIdWorker() { HashSet<long> set = new HashSet<long>(); IdWorker idWorker1 = new IdWorker(0, 0); IdWorker idWorker2 = new IdWorker(1, 0); Thread t1 = new Thread(() => DoTestIdWoker(idWorker1, set)); Thread t2 = new Thread(() => DoTestIdWoker(idWorker2, set)); t1.IsBackground = true; t2.IsBackground = true; t1.Start(); t2.Start(); try { Thread.Sleep(30000); t1.Abort(); t2.Abort(); } catch (Exception e) { } Console.WriteLine("done"); } private static void DoTestIdWoker(IdWorker idWorker, HashSet<long> set) { while (true) { long id = idWorker.nextId(); if (!set.Add(id)) { Console.WriteLine("duplicate:" + id); } Thread.Sleep(1); } }
snowflake算法可以根据自身项目的需要进行一定的修改。比如估算未来的数据中心个数,每个数据中心的机器数以及统一毫秒可以能的并发数来调整在算法中所需要的bit数。
优点:
1)不依赖于数据库,灵活方便,且性能优于数据库。
2)ID按照时间在单机上是递增的。
缺点:
1)在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。