你真的了解跳跃表吗

最近换了工做,由于工做的须要,也正好本身想好好研究一下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或者搜索西加加语言就行

相关文章
相关标签/搜索