索引这种常用的技术解决思路,底层往往会依赖哪些数据结构?
1. 为什么需要索引
实际的软件开发中,它们的本质都可以抽象为“对数据的存储和计算”。
- 存储,增删改查。一旦存储的数据很多,性能就成了关注的重点
- “如何节省存储空间、提高数据增删改查的效率”,是设计的重点。不夸张地说,索引设计得好坏,直接决定了这些系统是否优秀。
2. 索引的需求定义
2.1 功能性需求
- 数据是格式化数据还是非格式化数据?要构建索引的原始数据,类型有很多。分为两类,一类是结构化数据,比如,MySQL数据;另一类是非结构化数据,比如网页。对于非结构化数据,需要做预处理,提取出查询关键词,对关键词构建索引。
- 数据是静态还是动态?如果原始数据是静态数据,不会有数据的增加、删除、更新操作,所以,在构建索引的时候,只需要考虑查询效率就可以了。大部分情况下,我们都是对动态数据构建索引,不仅要考虑到索引的查询效率,还需要动态更新索引。
- 索引存储在内存还是硬盘?存储在内存,查询速度比磁盘高。索引大的时候,内存有限,可能不得不将索引存在磁盘中。还可以一部分存在内存,一部分存在磁盘,兼顾内存消耗和查询效率。
- 单值查找还是区间查找?
- 单关键词查找还是多关键词组合查找?比如,搜索引擎中构建的索引,既要支持一个关键词的查找,比如“数据结构”,也要支持组合关键词查找,比如“数据结构 & 算法”。对于多关键词查询来说,要分多种情况。像MySQL这种结构化数据的查询需求,可以实现针对多个关键词的组合,建立索引;对于像搜索引擎这样的非结构数据的查询需求,可以针对单个关键词构建索引,然后通过集合操作,比如求并集、求交集等,计算出多个关键词组合的查询结果。
2.2 非功能性需求
- 不管是存在内存中还是磁盘中,索引对存储空间的消耗不能过大。
- 考虑索引查询效率的同时,还要考虑索引的维护成本。索引的目的是提高查询效率,但是,基于动态数据集合构建的索引,还要考虑索引的维护成本。因为在原始数据动态增删改的同时,也需要动态的更新索引。而索引的更新会影响到增删改操作的性能。
3. 构建索引常用的数据结构
常用来构建索引的数据结构,就是讲过的几种支持动态数据集合的数据结构。比如,散列表、红黑树、跳表、B 树。除此之外,位图、布隆过滤器可以作为辅助索引,有序数组可以用来对静态数据构建索引。
- 散列表增删改查操作的性能非常好,时间复杂度是O(1)。一些键值数据库,比如Redis、Memcache,就是使用散列表来构建索引的。这类索引,一般都构建在内存中。
- 红黑树作为一种常用的平衡二叉查找树,数据插入、删除、查找的时间复杂度是O(log n),也非常适合用来构建内存索引。Ext文件系统中,对磁盘块的索引,用的就是红黑树。
- B 树比红黑树来说,更适合构建存储在磁盘中的索引。B 树是多叉树,对相同个数的数据构建索引,B 树的高度要低于红黑树。查询时,读取B 树索引,需要的磁盘IO次数更少。所以,大部分关系型数据库索引,比如MySQL、Oracle,都是用B 树来实现的。
- 跳表也支持快速添加、删除、查找数据。而且,通过灵活调整索引结点个数和数据个数之间的比例,可以很好地平衡索引对内存的消耗及其查询效率。Redis中的有序集合,就是用跳表来构建的。
- 布隆过滤器有一定的判错率。尽管对于判定存在的数据,有可能并不存在,但是对于判定不存在的数据,那肯定就不存在。更大的特点,就是内存占用非常少。针对数据,构建一个布隆过滤器,存储在内存中。要查询时,先通过布隆过滤器,判定是否存在。如果判定数据不存在,就没必要读取磁盘中的索引了。对于数据不存在的情况,数据查询就更加快速了。
- 有序数组也可被作为索引。如果数据是静态的,可以把数据的关键词抽取出来,组织成有序数组,然后利用二分查找来快速查找数据。
4. 总结
架构设计离不开数据结构和算法。要想成长为一个优秀的业务架构师、基础架构师,数据结构和算法的根基一定要打稳。那些看似很惊艳的架构设计思路,实际上,都是来自最常用的数据结构和算法。