当待搜索的数据量极为庞大时,数据所对应的索引的数据量也会非常大。就拿最常见的倒排索引来说,特别是当用户查询的关键词是常用词时,这些词所对应的倒排列表可以达到几百兆,而将这样庞大的索引由磁盘读入内存,势必会严重增加检索响应时间,影响用户的搜索体验。为了解决这样的问题,学者们提出了一系列的索引压缩技术。
实际上,我们所要处理的数据类型多如牛毛,根据不同的要求,为这些数据设计的索引更是千变万化,最常见的有倒排索引,复杂一点的还有各种树形索引等等。要想总结出一种万能的索引压缩技术,实在是很难。但是压缩方法的基本原理却是相通的。本文,我将以倒排索引为例,介绍几种简单的索引压缩技术。之所以选择倒排索引,除了它通用性强之外,也是由于其具有普遍性:倒排索引由以下两部分构成:
- 词典,其实就是由字符串构成的列表;
- 倒排列表,其实就是由一系列数字;
其他类型索引,不过都是由字符以及数字构成的,所以说从倒排索引的压缩也就能延伸出对于其他索引的压缩方法。
词典压缩
下表是一个典型的倒排索引,由单词;文档频率(DF);倒排列表指针;3个部分组成
其中,词典部分的存储会浪费空间的根本原因在于分配给单词的空间是统一的,也就是说不论你的单词是像”we”这样短的,还是像”confidentiality”这样长的,都必须分配能够容纳最长单词的空间。所以比较直接的压缩方法是将这些单词连续地存储在一个区域中,而倒排列表中只是存储这些单词出现的起始位置。如下图所示:
显然,这种做法使得词典中不存在冗余的空间了,所有的单词相当于被合成为一个整的字符串。当然,当单词数量极大的时候,还可以通过存储单词位置之间的差值来替代真实的单词位置,以降低存储单词位置的空间消耗。比如原本单词位置应该是”1,5,10,13,20…”,可以存储为”1,4,5,3,7…”。
更进一步,我们可以将上述词典压缩技术改进。还是把单词连成一个整体存储,只不过存储前,对单词分组,比如两两一组。只在倒排索引中,为每两个单词存储他们的起始位置,如下:
实际操作时,可以根据单词的长度动态对单词分组。查找倒排索引时,先根据位置信息读取这个单词分组,再进一步读取每个单词。从而实现对单词索引的进一步压缩。这里面存在的问题是:如何区分一个单词分组中的所有单词,一般的做法是在每个单词结尾处标记一个终止符,比如上图中,我用’$’号分割这些单词。
倒排列表压缩
倒排列表一般的内容包括:文档编号、词频、词位置。一个3部分信息。而这3部分信息基本都是(或者说可以转化为)整数。所以对于这部分数据的压缩,基本上是根据以下2个原则进行:
对于递增整数序列,我们一般存储其数值之间的差值,而不是数值本身(这一点在上面词典压缩时已经用到了)。比如倒排列表中,文档编号和词位置一般都是递增的整数序列。
对于整数,采用合适的压缩算法,编码数据。
第一点不再赘述,我在这里主要谈一下第二点。首先介绍一下在压缩算法中常用的编码技术,一般是两种:一元编码和二进制编码,这两类编码是压缩算法的基础构件,因为本文中我们涉及的压缩算法,无论其内部工作原理如何,都最终将数字表示成这两类编码的混合。
一元编码(Unary Code):一般是对于大于0的整数使用。对于整数 X>1 ,编码结果由 X−1 个二进制数字1和最末尾的二进制数字0构成。例如对于3,编码为
110
;对于5,编码为11110
。显然这种编码方式适用于小整数,对于大整数,实在是相当不经济。二进制编码(Binary Code):这个大家很熟悉了,就是将整数转化为二进制字符串。
了解这两种编码之后,可以看一些经典的压缩算法了。由于压缩算法实在很多,其基本思想又都比较相似。所以我只介绍下面两类。
1. Elias- γ 算法 和 Elias- δ 算法
Elias提出了两种压缩算法,通过分解函数将待压缩的数字分解成2个因子,之后分别利用一元编码和二进制编码表示这2个因子。
(1) Elias- γ 算法
Elias- γ 压缩算法的分解函数为:
其中, x 为待压缩数字,
1110
, d=1 的宽度为3的二进制编码为001
,两个编码之间,以:
分隔,最后得到数字9的Elias- γ 压缩结果为1110:001
(2) Elias- δ 算法
Elias- δ 算法与Elias- γ 相似,可以看做是Elias- γ