跳跃表的实现还是一个链表,是一个有序的链表,在遍历的时候基于比较,但普通链表只能遍历,跳跃表加入了一个层(也叫索引)的概念,层数越高的元素越少,每次先从高层查找,再逐渐降层,直到找到合适的位置。从图中可以看到高层的节点远远少于底层的节点数,从而实现了跳跃式查找。
redis只在两个地方用到了跳跃表,一个是有序集合键(zset),另一个是在集群节点用作内部数据结构。
其实跳跃表是受多层链表
的想法启发设计得来的。而上层指针链表都是在下层的基础上选取的。
如果上一层的链表的节点个数,是下面一层的节点个数的一半,这样查找就非常类似于一个二分查找
。
跳跃表用随机的方式(抛硬币)来解决层数的问题,好奇为什么不直接用二分查找的方式去解决问题, 查找了知乎的回答,找到了答案:
试想一下,如果我们结构上强制着二分查找,相邻的两层链表上的节点个数严格按照2:1的对应关系,那么在插入新节点的时候,就会打乱这层对应关系,要维护这层关系,又必须把新插入的节点后边的所有节点重新进行调整,这又让时间复杂度退化为O(N),删除数据也有同样的问题。
跳跃表为了避免这一问题,就采用了随机层数的方式来巧妙的解决。
不要求上下相邻两层链表之间的节点个数有严格的对应关系,而是为每个节点随机出一个层数(level),新插入的节点就会根据自己的层数决定该节点是否在这层的链表上。
本文通过操作redis zset来实际感受
redis命令(可以看 关于Redis的Zset使用方法_jesus小超的博客-CSDN博客_redis zset)
在公司的laravel中,直接使用 Illuminate\Support\Facades\Redis
可以让我在 Redis facade 调用任何的 Redis 命令。Laravel 使用魔术方法来传递命令至 Redis 服务器,所以可以很方便使用zset
Redis::zadd('runoobkey', 2, 'redis');
Redis::zadd('runoobkey', 3, 'redis');
Redis::zadd('runoobkey', 4, 'redis');
dd(Redis::zrange( 'runoobkey', 0, 10, 'WITHSCORES'));
但是这里发现一个需要注意的问题,就是如果存入的score是浮点数,那么有可能redis那边取出时会出现精度问题,这是我这边的案例:
$arr = [
[sprintf("%.1f", 3.9), 'redis3.9'],
[sprintf("%.1f", 3.6), 'redis3.6'],
[sprintf("%.1f", 3.2), 'redis3.2'],
[sprintf("%.1f", 3.8), 'redis3.8'],
[sprintf("%.1f", 3.5), 'redis3.5'],
[4, 'redis4']
];
Redis::del('runoobkey');
foreach ($arr as $v) {
Redis::zadd('runoobkey', $v[0], $v[1]);
}
dd($arr, Redis::zrange( 'runoobkey', 0, 10, 'WITHSCORES'));
sprintf("%.1f",xxx)保证了在laravel端时数据传入就是一位小数,打印结果为
array:6 [▼ 0 => array:2 [▼ 0 => "3.9" 1 => "redis3.9" ] 1 => array:2 [▼ 0 => "3.6" 1 => "redis3.6" ] 2 => array:2 [▼ 0 => "3.2" 1 => "redis3.2" ] 3 => array:2 [▼ 0 => "3.8" 1 => "redis3.8" ] 4 => array:2 [▼ 0 => "3.5" 1 => "redis3.5" ] 5 => array:2 [▼ 0 => 4 1 => "redis4" ] ]array:6 [▼ "redis3.2" => "3.2000000000000002" "redis3.5" => "3.5" "redis3.6" => "3.6000000000000001" "redis3.8" => "3.7999999999999998" "redis3.9" => "3.8999999999999999" "redis4" => "4" ]
因此在取出时要四舍五入处理下
而在公司之前的spring boot项目中,因为使用的是org.springblade.core.tool.utils.RedisUtil
该类方法只支持SET(字符串) 、 HMSET (哈希)两种redis格式,因此不好做修改,只能在下个项目考虑更换调用的redis类来使用有序集合,从而在代码中使用redis的跳跃链表优化性能。
目前网上见到最多的案例是使用redis.clients.jedis,同事的建议是jedis已经很落后了,不建议使用