转自:http://github.tiankonguse.com/blog/2014/12/03/sphinx-token-inverted-sort.html
前言
sphinx 在创建索引前需要做下面几件事:有数据源(pSource),有分词器(pTokenizer),有停止词Stopword 和 字典(pDict),索引引擎。
我们假设 数据源是 mysql, 分词器是 utf8 分词器。
索引前背景介绍
第一步是准备数据源。
这里采用 mysql 数据源。
mysql 数据的特点是一行一个记录。
每个记录有相同的字段。
每个字段可能代表数字,字符串,时间,二进制等信息,我们都可以按字符串处理即可。
//数据源
CSphSource_MySQL * pSrcMySQL = new CSphSource_MySQL ();
CSphSource * pSource = pSrcMySQL;
第二步准备分词器和字典。
这里不多说分词器,以后会专门写一篇记录来讲解分词器。
分词器依靠字典,可以把一个字符串分割为一些词语(word)。
然后根据这些词语,我们可以把mysql的每条记录每个字段都分割为若干词语,这里成为分词。
分割后这个分词需要保留几个信息:什么分词,属于哪个记录,属于哪个字段,在字段中的位置。
分词我们会hash (crc32) 成一个数字,冲突了就当做一个词了。
记录标示就是用自增整数ID.
字段一般不会很多,我们假设最多255个,使用8位可以表示。 字段的位置不确定,但是一个字段的内容也不会很多,我们用24位表示足够了。
所以哪个字段和字段的哪个位置就可以用一个32位整数代替了。
这样一个分词就可以用三个整数<wordId, docId, pos>来表示了。
//分词器
pTokenizer = sphCreateUTF8Tokenizer ();
pSource->SetTokenizer ( pTokenizer );
//字典
CSphDict_CRC32 * pDict = new CSphDict_CRC32 ( iMorph );
pSource->SetDict ( pDict );
一个分词称为一个hit, 数据结构如下
struct CSphWordHit {
DWORD m_iDocID; //文档ID, 唯一代表一个记录
DWORD m_iWordID; //单词ID, 对单词的hash值,可以理解为唯一标示
DWORD m_iWordPos; //储存两个信息:字段位置(高8位)和分词的位置(低24位)
};
我们一条记录一条记录的把所有的记录都分词了,就得到一个分词列表了。
由于这个列表很大,我们需要分成多块储存,这里假设最多16块吧。
对于每块,储存前先排序一下,这样我们就得到 16 个 有序的数组了。
然后我们就可以创建索引了。
//索引
CSphIndex * pIndex = sphCreateIndexPhrase ( sIndexPath );
//开始创建索引
pIndex->Build ( pDict, pSource, iMemLimit )
其中 一切准备完毕后进入 Build 函数。
build 函数创建搜索
进入 build 函数后先准备内容。
在执行 build 函数时 ,先逐条读取记录,然后对每条记录的每个字段会进行分词(Next函数),存在 hit 数据结构中。
而且会把 hit 数据按指定块大小排序后压缩储存在 *.spr 文件中。
块信息储存在 bins 数组中,块数最多16块, 块数用 iRawBlocks 表示。
接下来就是关键的创建压缩索引了。
首先创建索引对象。
cidxCreate()
//打开索引文件,先写入 m_tHeader 信息 和 cidxPagesDir 信息。
fdIndex = new CSphWriter_VLN ( ".spi" );
fdIndex->PutRawBytes ( &m_tHeader, sizeof(m_tHeader) );
//cidxPagesDir 数组全是 -1
fdIndex->PutBytes ( cidxPagesDir, sizeof(cidxPagesDir) );
//打开压缩数据文件,先写入一个开始符 bDummy
fdData = new CSphWriter_VLN ( ".spd" );
BYTE bDummy = 1;
fdData->PutBytes ( &bDummy, 1 );