一个完整的ID生成器,并极大地降低了并发重复的概率

来源:百度文库 编辑:神马文学网 时间:2024/04/29 18:19:47
一个完整的ID生成器,并极大地降低了并发重复的概率
Posted on 2005-09-01 19:23浪淘沙 阅读(884)评论(7)  编辑 收藏收藏至365Key 所属分类:10.Janssen
前文一个ID生成器的确简单,不能解决并发带来的重复性问题。这里完整地写一个ID生成器。其中做了一些并发问题的处理。
原理:获取系统时间,并在其后加上一个随机数
sample:
将IdentityGenerator实例化后可直接获取
Embacle.Identity.IdentityGenerator ig = new Embacle.Identity.IdentityGenerator();
long identity = ig.GetIdentity();//identity就是生成的Id
也可以直接获取IdentityGenerator的静态实例,这样整个应用程序域内就只有一个实例。结果是一样的,内存挑剔者使用
long identity = Embacle.Identity.IdentityGenerator.GetInstance().GetIdentity();
输出样例:前8位是时间戳,刻度取到秒(TimestampStyle.SecondTicks),后3位是随机数,设置随机数长度为3下面是GetIdentity()输出结果样例,如果精度要求更高的话可以自行增加随机数长度或者刻度值,可通过GetItentity的重载方法实现。但程序没有捕捉Int64整型数字溢出异常,也就是说,如果设置时间戳刻度太精细或者随机数长度过长都可能导致溢出的异常。
如果要自定义该异常请自行更改GetIdentity(DateTime,long,int)签名的方法,在其中加入自己的验证逻辑,不过使用默认GenIdentity()是不会抛出这样的异常的。理论上支持1秒钟生成1000个不重复Id,但是作者自行测试发现在理论需求达到非得在1秒钟生成900个左右的Id或者更高的话,设置随机数长度3会导致重复检查率大大增加,如果有这一很BT的需求的话请考虑增加随机数长度试试看,或者增加时间戳刻度。我发现一般PC机的时钟刻度只能精确到10毫秒,要比10毫秒更精确本人没有任何办法。
21047037328
21047038123
21047039547
21047040005
21047038927
自定义时间戳刻度:
注意:三个参数:
startDateTime 起始时间
(默认为:2005-01-01 00:00 000)
TimestampStyle 是时间戳刻度枚举,为方便设置自定义时间戳刻度,如果能看懂换算值的话也可以自行输入具体刻度值 TimestampStyleTicks是自行输入时间戳刻度
(默认为:TimestampStyle.SecondTicks,即取到秒)
randomLength 是随机数长度
(默认为:3,即0-999)
Embacle.Identity.IdentityGenerator ig = new Embacle.Identity.IdentityGenerator();
long identity = ig.GetIdentity(4);自定义随机数长度为4
long identity = ig.GetIdentity(TimestampStyle.TenMillSecondTicks);自定义随机数长度为10毫秒精度
long identity = ig.GetIdentity(TimestampStyle.TenMillSecondTicks,4)同时设置上面两种精度
匆匆完成,请仔细斟酌使用,如果有改进意见或者想骂人可以直接回复该贴,或者发送邮件给我,注意这体现了您的个人修养
作者:Janssenkm
信箱:janssenkm#gmail.com(请将#改为@)
代码如下:
//简单Id生成器
//原理:获取系统时间,并在其后加上一个随机数
//
//sample:
// 将IdentityGenerator实例化后可直接获取
// Embacle.Identity.IdentityGenerator ig = new Embacle.Identity.IdentityGenerator();
// long identity = ig.GetIdentity();//identity就是生成的Id
//
// 也可以直接获取IdentityGenerator的静态实例,这样整个应用程序域内就只有一个实例。结果是一样的,内存挑剔者使用
// long identity = Embacle.Identity.IdentityGenerator.GetInstance().GetIdentity();
//
//
//输出样例:前8位是时间戳,刻度取到秒(TimestampStyle.SecondTicks),后3位是随机数,设置随机数长度为3
//下面是GetIdentity()输出结果样例,如果精度要求更高的话可以自行增加随机数长度或者刻度值,可通过GetItentity
//的重载方法实现。但程序没有捕捉Int64整型数字溢出异常,也就是说,如果设置时间戳刻度太精细或者随机数长
//度过长都可能导致溢出的异常。
//
//如果要自定义该异常请自行更改GetIdentity(DateTime,long,int)签名的方法,在其中加入自己的验证逻辑,不过
//使用默认GenIdentity()是不会抛出这样的异常的。理论上支持1秒钟生成1000个不重复Id,但是作者自行测试发现
//在理论需求达到非得在1秒钟生成900个左右的Id或者更高的话,设置随机数长度3会导致重复检查率大大增加,如果
//有这一很BT的需求的话请考虑增加随机数长度试试看,或者增加时间戳刻度。我发现一般PC机的时钟刻度只能精确到
//10毫秒,要比10毫秒更精确本人没有任何办法。
//
//    21047037328
//    21047038123
//    21047039547
//    21047040005
//    21047038927
//
// 自定义时间戳刻度:
// 注意:三个参数:
//    startDateTime    起始时间
//                    (默认为:2005-01-01 00:00 000)
//    TimestampStyle    是时间戳刻度枚举,为方便设置自定义时间戳刻度,如果能看懂换算值的话也可以自行输入具体刻度值    TimestampStyleTicks是自行输入时间戳刻度
//                    (默认为:TimestampStyle.SecondTicks,即取到秒)
//    randomLength    是随机数长度
//                    (默认为:3,即0-999)
// Embacle.Identity.IdentityGenerator ig = new Embacle.Identity.IdentityGenerator();
// long identity = ig.GetIdentity(4);//自定义随机数长度为4
// long identity = ig.GetIdentity(TimestampStyle.TenMillSecondTicks);//自定义随机数长度为10毫秒精度
// long identity = ig.GetIdentity(TimestampStyle.TenMillSecondTicks,4)//同时设置上面两种精度
// 匆匆完成,请仔细斟酌使用,如果有改进意见或者想骂人可以直接回复该贴,或者发送邮件给我,注意这体现了您的个人修养
//
//                                                        作者:Janssenkm
//                                                        信箱:janssenkm#gmail.com(请将#改为@)
using System;
using System.Text;
using System.Collections;
namespace Embacle.Itentity
{
/// 
/// 简单Id生成器
/// 

public class IdentityGenerator
{
/// 
/// 随机数缓存
/// 

private static Hashtable ht;
/// 
/// 时间戳刻度缓存
/// 

private long lastTimeStampStyleTicks;
/// 
/// 时间戳缓存(上一次计算ID的系统时间按时间戳刻度取值)
/// 

private long lastEndDateTimeTicks;
public IdentityGenerator()
{
if(ht==null)
ht = new Hashtable();
}
/// 
/// IdentityGenerator的静态实例
/// 

private static IdentityGenerator ig;
public IdentityGenerator GetInstance()
{
if(ig==null)
ig = new IdentityGenerator();
return ig;
}
/// 
/// 按照时间戳刻度计算当前时间戳
/// 

/// 起始时间
/// 时间戳刻度值
/// long
private long GetTimestamp(DateTime startDateTime,long timestampStyleTicks)
{
if(timestampStyleTicks==0)
throw new Exception("时间戳刻度样式精度值不符,不能为0或负数");
DateTime endDateTime = DateTime.Now;
long ticks = (endDateTime.Ticks - startDateTime.Ticks)/timestampStyleTicks;
return ticks;
}
/// 
/// 静态随机数生成器
/// 

private static Random random;
/// 
/// 获取随机数
/// 

/// 随机数长度
/// 
private long GetRandom(int length)
{
if(length<=0)
throw new Exception("随机数长度指派错误,长度不能为0或负数");
if(random==null)
random = new Random();
int minValue = 0;
int maxValue = int.Parse(System.Math.Pow(10,length).ToString());
long result = long.Parse(random.Next(minValue,maxValue).ToString());
return result;
}
/// 
/// 计算一个Id
/// 以2005-1-1 00:00 000为起始时间刻度
/// 

/// long
public long GetIdentity()
{
DateTime startDateTime = new DateTime(2005,1,1,0,0,0,0);
TimestampStyle timestampStyle = TimestampStyle.SecondTicks;
int randomLength = 3;
return GetIdentity(startDateTime,timestampStyle,randomLength);
}
/// 
/// 计算一个Id
/// 以2005-1-1 00:00 000为起始时间刻度
/// 

/// 时间戳刻度
/// long
public long GetIdentity(TimestampStyle timestampStyle)
{
DateTime startDateTime = new DateTime(2005,1,1,0,0,0,0);
int randomLength = 3;
return GetIdentity(startDateTime,timestampStyle,randomLength);
}
/// 
/// 计算一个Id
/// 

/// 随机数长度
/// long
public long GetIdentity(int randomLength)
{
DateTime startDateTime = new DateTime(2005,1,1,0,0,0,0);
TimestampStyle timestampStyle = TimestampStyle.SecondTicks;
return GetIdentity(startDateTime,timestampStyle,randomLength);
}
/// 
/// 计算一个Id
/// 

/// 时间戳刻度
/// 随机数长度
/// long
public long GetIdentity(TimestampStyle timestampStyle,int randomLength)
{
DateTime startDateTime = new DateTime(2005,1,1,0,0,0,0);
return GetIdentity(startDateTime,timestampStyle,randomLength);
}
/// 
/// 计算一个Id
/// 

/// 时间戳的起始时间
/// 时间戳刻度
/// 随机数长度
/// long
public long GetIdentity(DateTime startDateTime,TimestampStyle timestampStyle,int randomLength)
{
long timestampStyleTicks = long.Parse(timestampStyle.ToString("D"));
return GetIdentity(startDateTime,timestampStyleTicks,randomLength);
}
/// 
/// 计算一个Id
/// 

/// 时间戳的起始时间
/// 时间戳刻度(毫微秒单位)
/// 随机数长度
/// long
public long GetIdentity(DateTime startDateTime,long timestampStyleTicks,int randomLength)
{
//新一轮时间戳刻度更新后更新缓存
//如果该参数不变则不进行此更新
if(timestampStyleTicks!=lastTimeStampStyleTicks)
ht.Clear();
//取得时间戳(当前时间按刻度取值)
long timestamp = GetTimestamp(startDateTime,timestampStyleTicks);
//新一轮时间戳更新后更新缓存
if(timestamp!=lastEndDateTimeTicks)
ht.Clear();
//幂
long power = long.Parse(Math.Pow(10,randomLength).ToString());
//随机数
long rand = GetRandom(randomLength);
//生成结果(Id)
long result = timestamp * power + rand;
//如果发现重复
if(ht.ContainsKey(result))
{
//在随机数长度范围内再重复查找一次
for(int i=0;i{
rand = GetRandom(randomLength);
result = timestamp * power + rand;
//发现非重复的Id
if(!ht.ContainsKey(result))
{
//将新的Id加入HashTable缓存
ht.Add(result,result);
break;//找到一个同一时间戳内的Id即退出
}
}
//此处运行在当前时间戳内无法再继续生成Id的代码,如:
//
//throw new Exception("已无法生成更多Id,请增加时间戳刻度TimestampStyle或增加随机数长度randomLength");
}
else
{
//将新的Id加入HashTable缓存
ht.Add(result,result);
}
//记录当前一轮时间戳(当前时间按刻度取值)
this.lastEndDateTimeTicks = timestamp;
//记录当前一轮时间戳刻度
this.lastTimeStampStyleTicks = timestampStyleTicks;
return result;
}
}
/// 
/// 时间戳精度样式

/// 采用格里高利时间刻度单位-毫微秒(1秒 =10,000,000毫微秒 )
///    疑问:MSDN上写的一毫微秒为一百万分之一秒 =100,000,000毫微秒,但是我始终无法算出该值,
///    不知是我错了还是MSDN的问题实际得出的数字是1秒 =10,000,000毫微秒,认为是我错了的话请将
///    下面的每一项枚举值都加一个0即可
/// 

public enum TimestampStyle:long
{
/// 
/// 时间刻度精度取为1毫秒(此项无意义,因为一般PC机系统时钟只能精确到10毫秒)
/// 

MillSecondTicks = 10000,
/// 
/// 时间刻度精度取为10毫秒,这是一般PC机系统时钟的最小精度单位
/// 

TenMillSecondTicks = 100000,
/// 
/// 时间刻度精度取为100毫秒
/// 

HundredMillSecondTicks = 1000000,
/// 
/// 时间刻度精度取为1秒,即1000毫秒
/// 

SecondTicks = 10000000,
/// 
/// 时间刻度精度取为5秒
/// 

FiveSecondTicks = 50000000,
/// 
/// 时间刻度精度取为10秒
/// 

TenSecondTicks = 100000000,
/// 
/// 时间刻度精度取为1分种(60秒)
/// 

MinutesTicks = 600000000
}
}
经过一个并发测试:设置时间刻度为1秒,随机数长度为3,可以保证1秒钟内生成1000个不重复的ID。如果要求每秒生成100000个不重复的刻度,则设置随机数长度为5即可。
Feedback
# re: 一个完整的ID生成器,并极大地降低了并发的概率
2005-09-01 19:38 byNetCobra
可以问一下在什么场合不能使用GUID,一定要使用你的这种ID生成方式吗?
# re: 一个完整的ID生成器,并极大地降低了并发的概率
2005-09-01 19:41 by浪淘沙
这个不一定,看各人爱好了。只是提供另一个选择的机会
# re: 一个完整的ID生成器,并极大地降低了并发的概率
2005-09-01 19:46 byzitiger
字好小,能不能大点?
# re: 一个完整的ID生成器,并极大地降低了并发的概率
2005-09-02 00:20 by
这个问题其实还有简单的解决方法
首先,Win32函数 QueryPerformanceCounter 可以取时间精确到1微秒,但一般来说,只要精确到1毫秒就能满足要求了。
long 字段有8个字节,我们可以把其中5个字节用来保存启动程序时的时间,精度为1毫秒,估算一下,至少要34年才能复位。一般来说,不会有两个人在同一毫秒时刻启动程序的,所以不同的机器上,这个值可以认为不同。
后3个字节可以用来保存序号,可以定义一个全局变量,从某个给定的值开始,每次生成一个ID后,这个值加1。3个字节的范围是0~1677万,一般来讲,启动一次程序不会需要生成这么多的ID号的,所以也可以不考虑重复。
只要把这两个值组合一下就可以生成一个唯一的ID号,基本上不会重复。如果真的发生冲突的话,你可以买彩票去了,呵呵
# re: 一个完整的ID生成器,并极大地降低了并发的概率
2005-09-02 08:23 by悟
还是写在存储过程里好!自动+1,对于多CPU的场合可
1、字段:可设为Key或唯一索引
2、使用锁
# re: 一个完整的ID生成器,并极大地降低了并发的概率
2005-09-02 14:23 byEdward.Net
像你这样把已经生成过的Id仍然保留在内存中,迟早会因为大数据量使用而把内存很快就耗尽的。
# re: 一个完整的ID生成器,并极大地降低了并发的概率
2005-09-03 17:10 by浪淘沙
我并没有全部保存在内存中,只是在同一时间刻度期间保存,到了下一刻度就清空了。
如果设置时间刻度为1秒的话,就只是保存这一秒内生成的ID。下一秒到来就清空了。