基于数以百万级的账户信息里面进行账号的校验并不是一件容易的事,关键点在于具体账户信息的定位.本文的主题仅仅只是为了应用的实现,就不再去做散列和排序各种算法之间的对比.对于一百万的账户信息,使用多大的散列表进行存储,是需要考量的.对于散列腹得最多的应该属256桶的Hash Table,但是在最好的情况下一百万账户信息在256桶的散列表当中重合率达到了近4000,而再从这当中进行二次搜索,同样也是一个数量级的工作,于是可以往上扩充到65536(算法使用CRC16),这样子最好的情况下重合率为15.26,但是要在这些随机的账号当中达到如此的平衡度完全是不可能的.另外考量内存,65536桶各放置一结构指针,每结构指向一个单向链表,链表当中存储下一级指针(PNext),关键字(Key,以账号作为关键字)和指向账户信息的结构,其占用空间为65536 X 4(32位) + 1000000 X (4 + N + 4) + 1000000 X M,其中N为Key占用的空间,M为账户信息结构占用的空间.而其中账户信息占用的空间是一定的,用K来表示散列表占用的空间,则有总占用空间 = K + 1000000 X (4 + N + 4 + M) ,假定N为30,M为100,则固定的账户相关信息占用为132MB,16位的散列占用为263KB,这个值是相当小的,由于考虑到其重合率仍然有超过15,于是可以考虑扩大.那扩大多少合适呢?如果是20位,那占用空间应该是4.2MB,24位为67MB,67MB已经是比较大的了,所以就只需要从20位和24位中间进行考虑.20位,实际上拥有1048575个值,已经稍大于现有的账户数据一百万,而24位可以表示16777215个值,对于当前一百万的用户信息,即使扩充最多考虑只会是300~500万级别,远没有必要使用24位这么多.
在确定使用20位的Hash值之后,就需要相应的算法.我们所需要的是CRC20,而这算法并不常用,用得比较多的通常是CRC16和CRC32,相对其它算法来说,比较优的就是其速度快,而且现成.对于CRC20,实际上我们可以取CRC32的低20位,如此一来远比我们自己设计一个20位的摘要算法来得实在和效率.为了对想法做一个验证,下面就来做一个测试:
先生成一百万个不重复的账号,并以每行一个账号存储于文件当中,假定存储到:c:/Accounts1M.txt
==============================
//在线用户信息
POnlineUserInfo = ^TOnlineUserInfo;
TOnlineUserInfo = Record
Account: String; //账号 4+12(Bytes)
PassWord: String; //密码 4+16(Bytes)
end;
PHashItem = ^THashItem;
THashItem = record
Key: string; //关键字,账号
Value: Pointer; //POnlineUserInfo
PNext: PHashItem;
end;
Accounts: Array[0..$fffff] of Pointer; //Hash Table
//根据CRC32生成CRC20
function CRC20(Data: String): LongWord;
begin
Result := CRC32($ffffffff,PChar(Data),Length(Data)) and $fffff;
end;
//密码校验测试
function CheckPassword(Account: String; Password: String): Boolean;
var
_CRC20: DWORD;
pItem: PHashItem;
pUser: POnlineUserInfo;
begin
Result := false;
//根据账号计算CRC
_CRC20 := CRC20(Account);
//定位到相同Hash值的账户链表
pItem := Accounts[_CRC20];
//遍历匹配账号
while pItem <> Nil do begin
if pItem.Key = Account then begin
//关键字相符,取账账信息
pUser := POnlineUserInfo(pItem.Value);
//判断密码是否正确,正确则返回校验成功(True)
if pUser.PassWord = Password then Result := True;
Exit;
end;
pItem := pItem.PNext;
end;
end;
//加载账户信息
procedure TForm1.btnInitAccountsClick(Sender: TObject);
var
tls: TStringList;
I: Integer;
Account: String;
_CRC20: DWORD;
pItem: PHashItem;
pUser: POnlineUserInfo;
dwStart,dwEnd: DWORD;
begin
tls := TStringList.Create;
try
tls.LoadFromFile('c:/Accounts1M.txt');
dwStart := GetTickCount;
for I := 0 to tls.Count - 1 do
begin
//取得账号
Account := tls.Strings[i];
//计算CRC值
_CRC20 := CRC20(Account);
//根据CRC值存储到同CRC的账户信息链表当中
if Accounts[_CRC20] = Nil then begin
new(pItem);
pItem^.Key := Account;
pItem^.PNext := Nil;
new(pUser);
pUser^.Account := Account;
pUser^.PassWord := Account; //以账号作为其密码
pItem^.Value := pUser;
Accounts[_CRC20] := pItem;
end else begin
pItem := Accounts[_CRC20];
while (pItem.PNext <> Nil) and (pItem.Key <> Account) do pItem := pItem.PNext;
if pItem.Key <> Account then begin
new(pItem.PNext);
pItem := pItem.PNext;
pItem.PNext := Nil;
pItem.Key := Account;
new(pUser);
pUser^.Account := Account;
pUser^.PassWord := Account; //以账号作为其密码
pItem.Value := pUser;
end;
end;
end;
dwEnd := GetTickCount;
//显示初始化完所有账号所需要的时间
ShowMessage(IntToStr(dwEnd-dwStart));
finally
tls.free;
end;
end;
//对所有账号进行一次全盘校验
procedure TForm1.btnCheckAllAccountsClick(Sender: TObject);
var
tls: TStringList;
I: Integer;
Account: String;
_CRC32,_CRC20: DWORD;
pItem: PHashItem;
pUser: POnlineUserInfo;
dwStart,dwEnd: DWORD;
begin
tls := TStringList.Create;
try
tls.LoadFromFile('c:/Accounts1M.txt');
dwStart := GetTickCount;
for I := 0 to tls.Count - 1 do
begin
Account := tls.Strings[i];
Randomize;
//根据随机数,测试是否使用正确的密码进行校验
if (Random($7fffffff) mod 2) = 0 then
CheckPassword(Account,Account)
else
CheckPassword(Account,'Account');
end;
dwEnd := GetTickCount;
//显示校验完所有账号所需要的时间
ShowMessage(IntToStr(dwEnd-dwStart));
finally
tls.free;
end;
end;
==============================
测试环境:
OS 名称: Microsoft(R) Windows(R) Server 2003, Enterprise Edition
OS 版本: 5.2.3790 Service Pack 2 Build 3790
系统型号: Latitude D630
系统类型: X86-based PC
处理器: 安装了 2 个处理器。
[01]: x86 Family 6 Model 15 Stepping 13 GenuineIntel ~777 Mhz
[02]: x86 Family 6 Model 15 Stepping 13 GenuineIntel ~777 Mhz
BIOS 版本: DELL - 27d80614
测试结果:
初始化100万账号信息:550-ms
校验完100万账号信息:2600-ms