最近換了工做,由於工做的須要,也正好本身想好好研究一下Java這門牛逼的語言,看了一下ElasticSearch和Lucene的源碼,以前歷來沒有寫過也沒有看過Java的東西,因此也算是惡補了一下Java吧,因爲是從C程序員開始的,因此對這種帶虛擬機的語言總有一些偏見,老以爲內存很差控制,因此一直以來都沒有怎麼碰過Java,最近靜下心來好好看了一下Java和相關的源碼,除了感受語言自己囉嗦了一點,仍是不錯的,可是有一點比較受不了就是基本上用vi很難作Java開發,要是沒有IDE的話,感受寫Java有些蛋疼啊。程序員
接下來一段時間會多聊一聊ElasticSearch和lucene相關的,由於最近也在研究這個,先看了Lucene的底層代碼,確實寫得簡潔明瞭,後面有機會會好好寫寫這方面的東西。算法
好了,不閒扯了,今天想說一說搜索引擎或者數據庫中索引(主要是倒排索引)的字典結構,一個好的高效的字典結構直接影響到索引的效果,而索引的構建其實並非徹底追求速度,還有磁盤空間,內存空間等各個因素,因此在一個索引系統中,須要權衡各個關係,找到一種適合你當前業務的數據結構進行存儲。這樣才能發揮索引最大的能效,通常狀況下,對於索引來講(主要是倒排索引)的字典來講,有跳躍表,B+樹,前綴樹,後綴樹,自動狀態機,哈希表這麼幾種數據結構,其實只要是一個快速的查找型的數據結構就能夠用來作索引的字典。數據庫
咱們從簡單的開始,一個一個來講說,今天先說說跳躍表,跳躍表結構很是很是簡單,可是,你真的瞭解它麼?編程
跳躍表是一種簡單,高效的快速查找結構,實現起來成本最小,而且速度也很快,只須要一個圖就能夠完美的解釋跳躍表的樣子,並且對於編程人員來講,要實現一個跳躍表看着圖就能實現,如下就是跳躍表的結構圖,沒有什麼難度。數組
跳躍表有幾個特色,這種特色對於某些類型的查詢是有至關的效率提高的。緩存
它是有序的,跳躍表的特色就是有序的,因此對於一些有序類型的數據,好比整數,日期這種,用跳躍表能夠進行範圍查找。微信
在構建跳躍表和查詢跳躍的複雜度一致,因此也比較適合於在線的實時索引,能夠來一個文檔,一邊查找就一邊知道要如何進行插入操做了。數據結構
保存到磁盤和從磁盤載入也比較簡單,由於本質上是幾個鏈表,因此保存的時候能夠按照數組的方式分別保存幾個數組就能夠了。性能
在lucene中,跳躍表並無用來存儲字典,而是用來存儲docid鏈,這裏後面咱們說lucene底層和Elasticsearch的時候再說具體結構吧,這篇咱們僅用來討論用跳躍表存字典的狀況。優化
對於跳躍表,咱們看看有一些什麼樣的優化方式可讓其更加適應一些場景。優化的話,咱們通常從空間和時間兩個方面來考慮一個優化,對於空間的話,又分紅內存空間優化和磁盤空間優化,固然通常首先考慮內存的優化,對於時間來講,也分紅構建時間和查詢時間兩個方面來優化,空間和時間是兩個相互矛盾的優化,具體到實際操做上如何取捨就要看具體的場景了。
若是咱們的內存空間不夠或者說跳躍表存儲的序列太長了,那麼咱們能夠把跳躍表的最底層的鏈表存儲在磁盤上,這是一次優化狀況,那麼檢索的時候須要一次到屢次磁盤才能檢索到數據,至關於用一部分性能來得到更大的數據加載能力。
若是還須要繼續優化的話,那麼能夠把上面幾層的節點的數據項變成指針,都指向磁盤的偏移地址上,那麼就更加的節省空間了,這樣又犧牲了一部分檢索性能,由於每一次讀取一個節點,不論是不是底層節點,都須要讀取一次磁盤來得到數據,對於上面兩個優化方式,對應的數據結構的圖以下,能夠看到這樣優化下去,內存的使用量會變得很小了。
可是上圖這種存儲方式不適合動態的增長或者刪除節點,由於一次這樣的更新操做須要操做好幾回磁盤,而且會致使磁盤上各個節點是不連續的,很是影響效率,因此比較適合那種寫入之後就不會變化的跳躍表的狀況。
最簡單的時間優化,那就是把數據所有加載到內存,直接查詢速度就快起來了,這個沒什麼難度,固然也可用用LRU這種緩存算法來折中一下,不消耗太大的空間而且也比直接放磁盤要快一些,或者用mmap讓操做系統來幫你作這個事情也能夠,不過使用LRU或者mmap的話,編程的難度和數據結構的設計難度就會要變難很多,得看你實現出來的成本了。
還有一種方式就是在查找算法上優化一下,用二分查找代替直接遍歷,這也只適合靜態的狀況,須要修改一下數據結構,將每一層的鏈表變成數組載入到內存中,這樣查找的時候能夠經過二分快速的定位到節點上。
跳躍表的層級的增長,通常狀況下是經過一個機率來計算是否要增長層級節點的,可是對於一些特殊的類型,其實在構建跳躍表的時候是能夠特殊處理的,好比跳躍表用來存儲時間序列,那麼咱們其實能夠每當時間過去了一分鐘或者一小時或者一天就增長一個層級,假設最小的時間維度是秒,若是一分鐘和一小時增長層級的話,那麼一天的數據就是三層,並且第一層最多24個節點,第二層最多1440個節點,最底層86400個節點,把第一層和第二層徹底載入內存的話應該說沒有任何壓力,甚至爲了查詢速度,第一層和第二層節點數固定下來,就是24和1440,這樣查詢的話都不用遍歷鏈表了,直接能夠經過運算就能求出下標而後直接跳到最底層上面來了。這是個典型的用了必定的內存空間來交換出更快的查詢時間。
上圖中的底層表示秒,第二層表示分鐘,第一層表示小時,那個紅色的節點表示那一分鐘實際上是沒數據的,爲了把節點數固定下來虛擬出來的節點,這樣能夠提升查詢的效率。
上面兩個大類型的優化,其實不少地方是矛盾的,具體取捨的時候就要看你的業務場景了,假設須要用跳躍表來存儲你的主鍵,你的業務場景是更新操做不多,查詢操做主要針對其餘字段而非主鍵的話,那麼底層存磁盤上,上面幾層的數據項也存磁盤上,而且經過LRU或者mmap交換內存和磁盤空間的跳躍表比較適合你。若是用來存儲分詞後的關鍵字的話,由於中文分詞之後關鍵詞的量級通常在幾十萬這個級別,那麼直接載入內存的話也能接受,因此直接加載到內存的方式可能更適合你。
好了,今天先寫這麼多,後面還有不少字典結構能夠優化的,慢慢來講,正好最近本身也在研究索引的優化,能夠留言討論哈,有說得不對的,隨便拍。
若是你以爲不錯,歡迎轉發給更多人看到,也歡迎關注個人公衆號,主要聊聊搜索,推薦,廣告技術,還有瞎扯。。文章會在這裏首先發出來:)掃描或者搜索微信號XJJ267或者搜索西加加語言就行