我们假设有这样一张商业信息数据表,我们用什么样的算法和结构(索引技术)可以快速方便实现搜索功能呢?
Sid | Subject | Type | Price | id | Description | PostTime |
0 | Sell apple | Sale | 50 | 101 | We are one of the largest … | 2002-10-02 |
1 | Buy Digital Camera | Buy | 200 | 102 | Our company mobile-point is situated in the … | 2003-02-10 |
2 | Sell dog shoes | Sale | 45 | 103 | Dog Shoes Model Number: 02GLPS074 Place of … | 2003-12-18 |
3 | Buy Toys And Jewelry | Buy | 1000 | 104 | I am interested in items that can be ordered in small … | 2003-08-20 |
(商业信息数据表)
一般我们需要的查询的需求分为下面两类:
a. 希望通过某个关键字找到所需信息(根据关键字来搜索)
b. 希望能够根据type, price, PostTime, id等字段进行检索(单字段信息检索)
1.建立索引
对于a需求:
我们使用 hashtable来达到快速检索的目的,既将可能用于查询的关键字作为hashtable的key(主键),跟该key有关的记录信息(我们称为TokenInfo)作为key对应的值。主键key来源于用于关键字查询的字段的文本(这些字段一般都要求是文本类型),一般一段文本可以分解出许多key,我们把分解的过程称为分词,分解出来的key称为Token。每一个Token是用于关键字查询的最小单位。TokenInfo一般会记录该Token的所在的文档ID(Doc Number), 所在文本的位置(Prox),在文本中出现的次数(Freq),所在的字段等等。详细结构如下:
Token Size |
| Token | DocFreq | DocList(链表) | ProxDelta(文件位置指针) | ||||
Token | TokenInfo | (TokenInfo) | |||||||
Token | TokenInfo |
| |||||||
… | … … |
| Doc Number | Token Freq | Field Bit | 下一个节点内容 … | |||
Token | TokenInfo | (DocList链表) | |||||||
(Token HashTable)
ProxDelta(文件位置指针)
N0 | A(0,0) | A(0,1) | … | A(0,n0-1) | N1 | A(1,0) | A(1,1) | … | A(1,n1-1) | … … |
第1个doc中该key出现的位置情况 | 第2个doc中该key出现的位置情况 | … … |
(详细的HashTable结构图)
查询的时候我们会用同样的分词算法把用户输入的关键分解为多个Token,每一个Token都去找这个HashTable, 然后将每一个Token查到的结果集进行合并,返回给用户。
对于b需求:
一般字段有下面几种类型:TEXT, STRING, ENUM, RANGE, NUMBER, BIT, DATE等。一般我们是将TEXT, STRING, NUMBER类型的字段采用上述HashTable的方法建立索引,只不过他们的分词方法是不一样的,其中TEXT类型的字段是要进行分词的,STRING类型的字段是不需要分词的,整体作为一个Token,NUMBER类型的字段是将字段转化为数字作为Token,这样可以节省空间;我们将ENUM, RANGE, BIT类型的字段采用BitMap的方法建立索引,下面具体说明BitMap的索引结构(以Type字段为例):
Type Value | Bit Map |
Buy | … … xxxx xxxx xxxx xxxx 0000 0000 0000 1010 |
Sale | … … xxxx xxxx xxxx xxxx 0000 0000 0000 0101 |
… | … … … … |
^doc#16 ^doc#1
我们再举另外一个例子(用结构体来表示):
假定我们有两个enum类型的字段,每一个类型有几个可能的值,共20条记录:
Field:
Country - China, Hong Kong, Japan, Korea, USA
Color - Blue ,Red, White
Records:
China-blue, Hong Kong-blue, Japan-red, China-red, China-white,
0-0 1-0 2-1 0-1 0-2
USA-red, Korea-white, China-white, China-white, USA-blue,
4-1 3-2 0-2 0-2 4-0
China-red, USA-red, USA-blue, Hong Kong-white, Japan-Red,
0-1 4-1 4-0 1-2 2-1
China-red, China-Red, China-BLUE, China-white, China-RED,
0-1 0-1 0-0 0-2 0-1
这些数据就可以用下面的结构体来表示:
SEnumDesc {
iNumOfFields = 2;
pField[0] = "Country", pField[1] = "Color";
pValues[0] = {
iNumOfValues = 5;
pVal[0] = "china", pVal[1] = "hong kong", pVal[2] = "japan",
pVal[3] = "korea", pVal[4] = "usa";
},
pValues[1] = {
iNumOfValues = 3;
pVal[0] = "blue", pVal[1] = "red", pVal[2] = "white";
};
iNumOfDocs = 20;
pBitmap[0] {
pMap[0][0] = xxxx xxxx xxxx 1111 1000 0101 1001 1001; // china
pMap[1][0] = xxxx xxxx xxxx 0000 0010 0000 0000 0010; // hong kong
pMap[2][0] = xxxx xxxx xxxx 0000 0100 0000 0000 0100; // japan
pMap[3][0] = xxxx xxxx xxxx 0000 0000 0000 0100 0000; // korea
pMap[4][0] = xxxx xxxx xxxx 0000 0001 1010 0010 0000; // usa
// ^doc#20 ^doc#1
}
pBitmap[1] {
pMap[0][0] = xxxx xxxx xxxx 0010 0001 0010 0000 0011; // blue
pMap[1][0] = xxxx xxxx xxxx 1001 1100 1100 0010 1100; // red
pMap[2][0] = xxxx xxxx xxxx 0100 0010 0001 1101 0000; // white
// ^doc#20 ^doc#1
}
如果一个字段是Range类型的,或者搜索的时候是要根据一个范围来查找的,比如说price字段,每一条记录都一个价格值,但是搜索的时候我们一般是根据一个价格范围来查找,对于这样的字段我们建立索引的时候也是采用BitMap结构,既先自己定义几个范围,一个记录该字段的值属于哪个范围,我们就将位置上设1。以price字段为例。
Range | Bit Map |
<50 | … … xxxx xxxx xxxx xxxx 0000 0000 0000 0100 |
[50,100) | … … xxxx xxxx xxxx xxxx 0000 0000 0000 0001 |
[100,500) | … … xxxx xxxx xxxx xxxx 0000 0000 0000 0010 |
>=500 | … … xxxx xxxx xxxx xxxx 0000 0000 0000 1000 |
^doc#16 ^doc#1
对于Bit类型的字段同样也是采用BitMap的结构,这里就不在阐述了。BitMap结构的好处是节省空间,结果集合逻辑运算简单快速,但并不是所有Enum类型的字段都采用BitMap结构,当枚举值大于32个的时候,采用BitMap就不方便了,这个时候我们会采用HashTable的结构来建立索引。