1、Redis数据结构的底层实现怎么做的
(1) 字符串处理(string)
string的底层实现
没有数据结构支撑的几个问题
-
极其容易造成缓冲区溢出问题,比如用
strcat()
,在用这个函数之前必须要先给目标变量分配足够的空间,否则就会溢出。 -
如果要获取字符串的长度,没有数据结构的支撑,可能就需要遍历,它的复杂度是O(N)
-
内存重分配。C字符串的每次变更(曾长或缩短)都会对数组作内存重分配。同样,如果是缩短,没有处理好多余的空间,也会造成内存泄漏。
Redis自己构建了一种名叫Simple dynamic string(SDS)
的数据结构,他分别对这几个问题作了处理。
优点:
-
开发者不用担心字符串变更造成的内存溢出问题。
-
常数时间复杂度获取字符串长度
len字段
。 -
空间预分配
free字段
,会默认留够一定的空间防止多次重分配内存。
(2) 链表
Redis的链表在双向链表上扩展了头、尾节点、元素数等属性。
Redis的链表有这几个特点:
可以直接获得头、尾节点。
常数时间复杂度得到链表长度。
是双向链表。
(3) 字典(Hash)
Redis的Hash,就是在
数组+链表
的基础上,进行了一些rehash优化等。可以看出:
Reids的Hash采用链地址法来处理冲突,然后它没有使用红黑树优化。
哈希表节点采用单链表结构。
rehash优化。
(4) 跳跃表
这个数据结构是我面试中见过最多的,它其实特别简单。学过的人可能都知道,它和平衡树性能很相似,但为什么不用平衡树而用skipList呢?
skipList & AVL 之间的选择
从算法实现难度上来比较,skiplist比平衡树要简单得多。
平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当。
在做范围查找的时候,平衡树比skiplist操作要复杂。
skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的。
可以看到,skipList中的元素是有序的,所以跳跃表在redis中用在有序集合键、集群节点内部数据结构
(5) 整数集合(intset)
Reids对整数存储专门作了优化,intset就是redis用于保存整数值的集合数据结构。当一个结合中只包含整数元素,redis就会用这个来存储。
(6) 压缩列表(ziplist)
ziplist是redis为了节约内存而开发的顺序型数据结构。它被用在列表键和哈希键中。一般用于小数据存储。
(7) 快速列表(quicklist)
一个由ziplist组成的双向链表。但是一个quicklist可以有多个quicklist节点,它很像B树的存储方式。是在redis3.2版本中新加的数据结构,用在列表的底层实现。
2、虚拟内存了解吗?具体调度方式?
虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常 是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。与没有使用虚拟内存技术的系统相比,使用这种技术的系统使得大 型程序的编写变得更容易,对真正的物理内存(例如RAM)的使用也更有效率。
注意:虚拟内存不只是“用磁盘空间来扩展物理内存”的意思——这只是扩充内存级别以使其包含硬盘驱动器而已。把内存扩展到磁盘只是使用虚拟内存技术的一个结果,它的作用也可以通过覆盖或者把处于不活动状态的程序以及它们的数据全部交换到磁盘上等方式来实现。对虚拟内存的定义是基于对地址空间的重定义的,即把地址空间定义为“连续的虚拟内存地址”,以借此“欺骗”程序,使它们以为自己正在使用一大块的“连续”地址。
具体调度
调度方式有分页式、段式、段页式3种。
页式调度是将逻辑和物理地址空间都分成固定大小的页。主存按页顺序编号,而每个独立编址的程序空间有自己的页号顺序,通过调度辅存中程序的各页可以离散装入主存中不同的页面位置,并可据表一一对应检索。页式调度的优点是页内零头小,页表对程序员来说是透明的,地址变换快,调入操作简单;缺点是各页不是程序的独立模块,不便于实现程序和数据的保护。段式调度是按程序的逻辑结构划分地址空间,段的长度是随意的,并且允许伸长,它的优点是消除了内存零头,易于实现存储保护,便于程序动态装配;缺点是调入操作复杂。将这两种方法结合起来便构成段页式调度。在段页式调度中把物理空间分成页,程序按模块分段,每个段再分成与物理空间页同样小的页面。段页式调度综合了段式和页式的优点。其缺点是增加了硬件成本,软件也较复杂。大型通用计算机系统多数采用段页式调度。