首页 > 代码库 > 看Lucene源码必须知道的基本规则和算法
看Lucene源码必须知道的基本规则和算法
上中学的时候写作文,最喜欢的季节我都是写冬天。虽然是因为写冬天的人比较少,那时确实也是对其他季节没有什么特殊的偏好,反而一到冬天,自己皮肤会变得特别白。但是冬天啊,看到的只有四季常青盆栽:瓜栗(就是发财树,好吧,算我矫情,反正我不喜欢这个名字),绿萝,永远看不到它开花的巴西铁,富贵竹,散尾葵……过年的时候家里的杜鹃就开花了,零星的几朵小花儿更突显了这个季节的凄凉。红掌,蝴蝶兰总是美美的在那里,开不败却看不到生机。插到水里的勿忘我,洋桔梗,看到他们也只会联想到过几天他们会枯萎的命运。春天来了,先是迎春花,然后是桃花,玉兰。到了四月,红叶碧桃,紫荆,樱花,紫叶李,垂丝海棠……最喜欢丁香花的味道~~再过几日,郁金香和牡丹也该开了。桃之夭夭,灼灼其华。果然,阳光下这些花儿是流光溢彩的。人生的悲哀不是短暂的快乐过后无尽的痛苦,而是从来没让自己快乐过。想想小鲜肉看的《熊出没-雪岭熊风》电影,熊二没有再次遇到团子之前的魂儿不守舍,与团子经历过精彩之后,虽然别人什么都不记得了,所有的场景回到了最初,熊二心里却是满足和平静。就像这些花儿,虽然是花开不多时,但怒放过的青春总好过冬青一日和一生毫无区别(中学作文里还总是在赞扬它冬天还是绿的呢[此处有表情])。大概现在和中学的时候最大的区别,就是那时候的人生观更多的是受父母的影响。父母都是医生,铁饭碗,稳定是一成不变的追求。离父母越来越远,活得越来越像自己,才发现自己的人生需要冬天的期待与思考,春天花的妖娆,夏天叶的茂盛,秋天果实的沉重。谁规定的第一个季节是春天?我的人生第一个季节就不是
下面介绍一些Lucene使用基本规则和算法。这些规则和算法的选择,都和Lucene和支持TB级的倒排索引有关。
前缀后缀规则(Prefix+Suffix):在Lucene的反向索引中,要保存词典的信息,所有的词再词典中是按照字典顺序进行排列的,然后词典中包含了文档中的几乎所有的词,并且有的词还是很长的,这样索引文件会非常的大,所谓前缀后缀规则,就是某个词和前一个词有共同的前缀的时候,后面的词仅仅保存前缀在词中的偏移(offset),和剩下的部分(后缀)。
比如:北京天安门 这个词词典里通常都会包含北京 天安门 北京天安门 这三个词。北京和北京天安门由于前缀相同,在字典表里会相邻存储,两个词存成 北京2天安门 ,这样存比北京北京天安门 省空间。
差值规则(Delta):在lucene的反向索引中,需要保存很多整形数字的信息,比如文档ID号,比如词在文档中的位置等等。整形数字是以可变长整型的格式存储的。随着数值的增大,每个数字占用的比特位增多。所谓差值规则就是先后保存两个整数的时候,后面的整数仅仅保存和前面整数的差即可。多唠叨两句:因为看到有的哥哥们定义数据库字段的时候总是想都不想就用varchar,MD5的结果也用varchar[汗]。MD5的结果长度是固定的,没有必要用varchar来节省空间。定长的char效率会高些。
LZ4算法(Realtime Compression Algorithm):在操作系统(linux/freeBSD),文件系统(OpenZFS),大数据(Hadoop),搜索引擎(Lucene/solr),数据库(Hbase)等中都可以看到它的身影,很通用。压缩/解压速度快。
跳跃表规则(Skip list):跳跃表是一种数据结构。额~~,要不能用几句话把它介绍明白,真不好意思说自己有那么多算法专利。首先使用跳跃表的前提是因为搜索引擎的索引数据是高度有序的。打个比方:我从北京回老家青州市可以做北京南到青岛的动车或者高铁。它们的路线是一样的,后者贵100块钱。贵在哪里呢?后者停的站少,就是跳站了。有的高铁到青州市不停。我只能在前一站淄博或者后一站潍坊下车,然后坐慢车去青州市。跳跃表就是这个原理。所有的搜索数据存在一个链表里,这就是慢车(最传统的绿皮车)。然后新加一个链表,存的数据中间有间隔(K字头车)。这时候我不得不说一个原则:所有原来的时间复杂度是delta(找这个符号比较费劲,我就直接用英文了,记住它是很有好处的,去米国总免不了和这个航空公司打交道~~) n的算法,期待的终极优化后的结果基本都是 delta log n。所以只有两层的话,时间复杂度是达不到要求的。怎样达到要求呢?最终要形成一棵树。怎么形成一棵树呢?加层呗。加大跳站的间隔,T字头车,D字头车,G字头车。一直到中间是所有的站,形成了一个root。树形结构就形成了。时间复杂度变成了delta log n[耶][耶] Lucene3.0之前很多地方使用这种数据结构来提高查找速度。但是因为它对模糊查询的支持不太好,现在Lucene改用FST了。
关于delta再多唠叨两句:它是希腊语的第四个字母,大写是△。我这么懒,不愿意去拷贝一个小写字母到这里,大写字母打出来也是因为我直接改用日语输入法,打个[三角形]出来的~~。上面提到差值规则和时间复杂度都用到了delta。因为在数学或者物理中大写的Δ用来表示增量符号。而小写通常在高等数学中用于表示变量或者符号。所以差值规则里用到了它大写的含义,时间复杂度里用到了它小写的含义。
有限自动机算法(FST,Finite State Transducer):通过输入有序字符串构建最小有向无环图。通过共享前缀来节省空间,内存存放前缀索引,磁盘存放后缀词块。Lucene的源码中可以看到它的具体实现。
Lucene之所以有那么频繁的版本升级,我以前还专门追剧似的关心这个升级,是因为这里面有一个问题的发生与解决的过程,举个简单的例子:在Windows系统中一个文件夹只能存放2W多个文件,在1W多个文件以后写入速度会急剧下降,Lucene这样处理TB级数据的系统更要考虑数据量和性能的关系和权衡。
有限自动机是Lucene的核心查找算法,理解需要一定的时间。下面介绍Lucene的打分相关规则,这部分很容易理解。
文档权重(Document boost):在索引时给某个文档设置的权重值。
域权重(Field boost):在查询的时候给某个域设置的权重值。
调整因子(Coord):基于文档中包含查询关键词个数计算出来的调整因子。一般而言,如果一个文档中相比其它的文档出现了更多的查询关键词,那么其值越大。
逆文档频率(Inerse document frequency):基于Term的一个因子,存在的意义是告诉打分公式一个词的稀有程度。其值越低,词越稀有(这里的值是指单纯的频率,即多少个文档中出现了该词;而非指Lucene中idf的计算公式)。打分公式利用这个因子提升包含稀有词文档的权重。
长度归一化(Length norm):基于域的一个归一化因子。其值由给定域中Term的个数决定(在索引文档的时候已经计算出来了,并且存储到了索引中)。域越的文本越长,因子的权重越低。这表明Lucene打分公式偏向于域包含Term少的文档。
词频(Term frequency):基于Term的一个因子。用来描述给定Term在一个文档中出现的次数,词频越大,文档的得分越大。
查询归一化因子(Query norm):基于查询语句的归一化因子。其值为查询语句中每一个查询词权重的平方和。查询归一化因子使得比较不同查询语句的得分变得可行,当然比较不同查询语句得分并不总是那么易于实现和可行的。
看Lucene源码必须知道的基本规则和算法