使用lucene進行查詢不可避免都會使用到其提供的字典功能,即根據給定的term找到該term所對應的倒排文檔id列表等信息。實際上lucene索引文件後綴名爲tim和tip的文件實現的就是lucene的字典功能。html
怎麼實現一個字典呢?咱們立刻想到排序數組,即term字典是一個已經按字母順序排序好的數組,數組每一項存放着term和對應的倒排文檔id列表。每次載入索引的時候只要將term數組載入內存,經過二分查找便可。這種方法查詢時間複雜度爲Log(N),N指的是term數目,佔用的空間大小是O(N*str(term))。排序數組的缺點是消耗內存,即須要完整存儲每個term,當term數目多達上千萬時,佔用的內存將不可接受。node
不少數據結構均能完成字典功能,總結以下。redis
數據結構 | 優缺點 |
排序列表Array/List | 使用二分法查找,不平衡 |
HashMap/TreeMap | 性能高,內存消耗大,幾乎是原始數據的三倍 |
Skip List | 跳躍表,可快速查找詞語,在lucene、redis、Hbase等均有實現。相對於TreeMap等結構,特別適合高併發場景(Skip List介紹) |
Trie | 適合英文詞典,若是系統中存在大量字符串且這些字符串基本沒有公共前綴,則相應的trie樹將很是消耗內存(數據結構之trie樹) |
Double Array Trie | 適合作中文詞典,內存佔用小,不少分詞工具均採用此種算法(深刻雙數組Trie) |
Ternary Search Tree | 三叉樹,每個node有3個節點,兼具省空間和查詢快的優勢(Ternary Search Tree) |
Finite State Transducers (FST) | 一種有限狀態轉移機,Lucene 4有開源實現,並大量使用 |
lucene從4開始大量使用的數據結構是FST(Finite State Transducer)。FST有兩個優勢:1)空間佔用小。經過對詞典中單詞前綴和後綴的重複利用,壓縮了存儲空間;2)查詢速度快。O(len(str))的查詢時間複雜度。算法
下面簡單描述下FST的構造過程(工具演示:http://examples.mikemccandless.com/fst.py?terms=&cmd=Build+it%21)。咱們對「cat」、 「deep」、 「do」、 「dog」 、「dogs」這5個單詞進行插入構建FST(注:必須已排序)。數組
1)插入「cat」數據結構
插入cat,每一個字母造成一條邊,其中t邊指向終點。併發
2)插入「deep」less
與前一個單詞「cat」進行最大前綴匹配,發現沒有匹配則直接插入,P邊指向終點。高併發
3)插入「do」工具
與前一個單詞「deep」進行最大前綴匹配,發現是d,則在d邊後增長新邊o,o邊指向終點。
4)插入「dog」
與前一個單詞「do」進行最大前綴匹配,發現是do,則在o邊後增長新邊g,g邊指向終點。
5)插入「dogs」
與前一個單詞「dog」進行最大前綴匹配,發現是dog,則在g後增長新邊s,s邊指向終點。
最終咱們獲得瞭如上一個有向無環圖。利用該結構能夠很方便的進行查詢,如給定一個term 「dog」,咱們能夠經過上述結構很方便的查詢存不存在,甚至咱們在構建過程當中能夠將單詞與某一數字、單詞進行關聯,從而實現key-value的映射。
咱們能夠將FST當作Key-Value數據結構來進行使用,特別在對內存開銷要求少的應用場景。Lucene已經爲咱們提供了開源的FST工具,下面的代碼是使用說明。
1 public static void main(String[] args) { 2 try { 3 String inputValues[] = {"cat", "deep", "do", "dog", "dogs"}; 4 long outputValues[] = {5, 7, 17, 18, 21}; 5 PositiveIntOutputs outputs = PositiveIntOutputs.getSingleton(true); 6 Builder<Long> builder = new Builder<Long>(FST.INPUT_TYPE.BYTE1, outputs); 7 BytesRef scratchBytes = new BytesRef(); 8 IntsRef scratchInts = new IntsRef(); 9 for (int i = 0; i < inputValues.length; i++) { 10 scratchBytes.copyChars(inputValues[i]); 11 builder.add(Util.toIntsRef(scratchBytes, scratchInts), outputValues[i]); 12 } 13 FST<Long> fst = builder.finish(); 14 Long value = Util.get(fst, new BytesRef("dog")); 15 System.out.println(value); // 18 16 } catch (Exception e) { 17 ; 18 } 19 }
FST壓縮率通常在3倍~20倍之間,相對於TreeMap/HashMap的膨脹3倍,內存節省就有9倍到60倍!(摘自:把自動機用做 Key-Value 存儲),那FST在性能方面真的能知足要求嗎?
下面是我在蘋果筆記本(i7處理器)進行的簡單測試,性能雖不如TreeMap和HashMap,但也算良好,可以知足大部分應用的需求。
http://sbp810050504.blog.51cto.com/2799422/1361551
http://blog.sina.com.cn/s/blog_4bec92980101hvdd.html
http://blog.mikemccandless.com/2013/06/build-your-own-finite-state-transducer.html
檢索實踐文章系列: