当前位置: 首页 > news >正文

物联网平台网站开发网络营销推广课程培训

物联网平台网站开发,网络营销推广课程培训,win8式网站后台模板,浙江嘉兴建设局网站是什么 是基于内存(而不是磁盘)的kv(而不是关系型mysql那种)数据库#xff0c;通过空间换时间 源码分析 跳表skiplist 假设你有个有序链表#xff0c;你想看某个特定的值是否出现在这个链表中#xff0c;那你是不是只能遍历一次链表才能知道#xff0c;时间复杂度为O(n…是什么 是基于内存(而不是磁盘)的kv(而不是关系型mysql那种)数据库通过空间换时间 源码分析 跳表skiplist 假设你有个有序链表你想看某个特定的值是否出现在这个链表中那你是不是只能遍历一次链表才能知道时间复杂度为O(n)。 可能有人会问为什么不直接用连续存储我们还能用二分查找用链表是想继续保留它修改时间复杂度低的优势。那我们如何优化单次查找的速度其实思路很像是二分查找但单链表无法随机访问的特性限制了我们但二分逐渐缩小范围的思路启发了我们能不能想什么办法逐渐缩小范围 我是不是可以在原链表之上新建一个链表新链表是原链表每隔一个节点取一个。假设原链表为L0新链表为L1L1中的元素是L0中的第1、3、5、7、9……个节点然后再建立L1和L0中各个节点的指针。这样L1就可以将L0中的范围缩小一半同理对L1再建立新链表L2……更高level的链表划分更大的区间确定值域的大区间后逐级向下缩小范围如下图。 假设我们想找13我们可以在先在L3中确定2-14的范围然后在L2中确定8-14的范围接着在L1中确定10-14的范围最后在L0中找到13整体寻找路径如下图红色路径是不是比直接在L0中找13的绿色路径所经过的节点数少一些 其实这种实现很像二分查找只不过事先将二分查找的中间点存储下来了用额外的空间换取了时间很容易想到其时间复杂度和二分查找一致都是O(logn)。 如果链表中插入或者删除了某个节点怎么办是不是每次数据变动都要重建整个数据结构 其实不必我们不需要严格保证两两层级之间的二分之一的关系只需要概率上为二分之一就行删除一个节点好说直接把该节点所在的每个层级中的对应节点删掉插入节点时新节点以指数递减的概率往上层链表插入即可。 比如L0中100%插入L1中以1/2的概率插入如果L1中插入了L2中又以1/2的概率插入…… 注意只要高Level中有的节点低Level中一定有但高Level链表中出现的概率会随着level指数即1/2的n次方递减最终跳表可能会长这个样子。 redis跳表数据结构 Redis中的有序集合(sorted set)的相关操作(比如zadd、zrange)其底层实现就是skiplist。我们接下来看下redis是如何实现skiplist的。 typedef struct zskiplist {struct zskiplistNode *header, *tail; // 头尾节点指针 unsigned long length; // 最底层节点个数 int level; // 最高多少级链表(最大层数redis默认设置为32即ZSKIPLIST_MAXLEVEL最底层可容纳2的32次方个节点) } zskiplist;我们先来看下redis中zskiplist的定义没啥内容就头尾指针、长度和级数重点还是在zskiplistNode中。zskiplistNode中是有前向指针的每个节点都有pre指针即下面的backward属性索引前一个节点所以跳表还是个双向链表 typedef struct zskiplistNode {sds ele; // 节点存储的具体值 double score; // 节点对应的权值 struct zskiplistNode *backward; // 当前节点的前一个节点的地址(由此可知跳表以双向链表为基础但也只在最底层是双向)struct zskiplistLevel {struct zskiplistNode *forward; // 当前节点在当前层的后向指针即下一个节点的地址 unsigned long span; // 当前节点当前层到下一个节点的跨度(以最底层作为基准相隔多少个节点) 就是数箭头数每一层最后一个节点的span就是该节点到(最底层)最后一个节点的距离即箭头数那你} level[];//多层 } zskiplistNode;redis中的skiplist实现稍微和我们上文中讲的不大一样它并不是简单的多级链表的形式而是直接在zskiplistNode中的level[]将不同level的节点的关联关系组织起来zskiplist的结构可视化如下。 redis跳表操作 创建跳表 /* 创建跳表 */ zskiplist *zslCreate(void) {zskiplist *zsl;zsl zmalloc(sizeof(*zsl));zsl-level 1;zsl-length 0;zsl-header zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL); // 创建含有32层的头节点for (int j 0; j ZSKIPLIST_MAXLEVEL; j) {zsl-header-level[j].forward NULL;zsl-header-level[j].span 0;}zsl-header-backward NULL;zsl-tail NULL;return zsl; }插入节点 /* 在跳表中插入一个新的节点, */ zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;unsigned int rank[ZSKIPLIST_MAXLEVEL];int i, level;serverAssert(!isnan(score));x zsl-header;/*这个函数从当前最高层数开始将每一层刚好小于(大于的前一个)新节点score值的节点保存在update[]数组中然后将该节点距离头节点的节点数(以L0为基准)保存在rank数组中例如在level3那层权值10的节点和头节点之间的距离为5如果这时新节点权值为11那么在L3即rank[3]5*/for (i zsl-level-1; i 0; i--) {/*每一层的rank的值都是在上一层的基础上接着进行计算因为一直向着权值变大的方向移动而没有后退最高层初始化为0*/rank[i] i (zsl-level-1) ? 0 : rank[i1];//在当前层与当前节点x的下一个节点比较新权值更大则向后移动while (x-level[i].forward (x-level[i].forward-score score ||(x-level[i].forward-score score sdscmp(x-level[i].forward-ele,ele) 0))){rank[i] x-level[i].span;x x-level[i].forward;}update[i] x;//第i层刚好小于新权值的节点}/* skiplist中不会出现重复的元素但我们允许重复的分值因为如果是调用zslInsert()的话不会出现重复插入两个相同的元素因为在zslInsert()中已经判断了hash表中是否存在*/level zslRandomLevel(); // 生成一个随机值确定最高需要插入到第几级链表里 /*如果超出当前记录的用到的最高层说明新节点在该最高层之后到新最高层level中都是头节点后的第一个节点*/if (level zsl-level) {for (i zsl-level; i level; i) {/*刚好小于新节点的节点就是头节点rank就是记录刚好小于新节点的节点到头节点的距离头节点和头节点之间距离当然就是0*/rank[i] 0;//刚好小于新节点的节点就是头节点update[i] zsl-header;/*联系到后面的那句x-level[i].span update[i]-level[i].span - (rank[0] - rank[i])而rank[i]0update[i]-level[i].span zsl-length即x-level[i].span zsl-length - rank[0]即二者共同使得新节点到头节点距离为*/update[i]-level[i].span zsl-length;}zsl-level level;}x zslCreateNode(level,score,ele); // 为插入的数据创建新节点 for (i 0; i level; i) {//将新节点插入到刚好小于之后刚好大于之前x-level[i].forward update[i]-level[i].forward;update[i]-level[i].forward x;/*新节点在第i层到下一个节点的距离*/x-level[i].span update[i]-level[i].span - (rank[0] - rank[i]);update[i]-level[i].span (rank[0] - rank[i]) 1;}/* 为其他level增加span值因为在原有俩节点之间插入了一个新节点 */for (i level; i zsl-level; i) {update[i]-level[i].span;}x-backward (update[0] zsl-header) ? NULL : update[0];if (x-level[0].forward)x-level[0].forward-backward x;elsezsl-tail x;zsl-length;return x; } int zslRandomLevel(void) {int level 1;/*random返回随机32位uint与运算相当于产生随机16位ZSKIPLIST_P是1/4那就是1/4概率为真相当于每想多一层就要再乘1/4效果就是每一层大致是其下一层节点数的1/4*/while ((random()0xFFFF) (ZSKIPLIST_P * 0xFFFF))level 1;return (levelZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;//不能超过最大层数 }更新节点 正如上面跳跃表添加节点时所说跳跃表上的节点数据有具有有序性的。所以更新表节点时也需要注意时候需要调整顺序如果更新后的节点在跳跃表中的排位没有变化则只需要更新节点上的score即可。如果更新后节点的排位发生改变则需要先将原先的节点删除然后新增一个score为新值的节点。 下面这个函数就是根据value即sds ele和原score即curscore找到需要更新的节点并更新为新score即newscore zskiplistNode *zslUpdateScore(zskiplist *zsl, double curscore, sds ele, double newscore) {zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;int i;//和查找一开始的动作一样x zsl-header;for (i zsl-level-1; i 0; i--) {while (x-level[i].forward (x-level[i].forward-score curscore ||(x-level[i].forward-score curscore sdscmp(x-level[i].forward-ele,ele) 0))){x x-level[i].forward;}update[i] x;}//如果curscore对应的节点时存在的那么最底层下x-level[0].forward一定是指向curscore对应的节点因为x-level[0]就是update[0]即最底层中刚好小于curscore的节点x x-level[0].forward;//判断节点是否一致serverAssert(x curscore x-score sdscmp(x-ele,ele) 0);//如果更新节点的score后这个节点在跳跃表中的排位没有变化那么就不需要进行复杂的删除节点添加节点的操作只需要更新一下节点的score即可if ((x-backward NULL || x-backward-score newscore) (x-level[0].forward NULL || x-level[0].forward-score newscore)){x-score newscore;return x;}//如果节点的排位发生改变那么调用zslDeleteNode函数来删除跳跃表中的这个节点信息比如各指针的关联但不是释放内存zslDeleteNode(zsl, x, update);//重新添加score的值为newscore的节点zskiplistNode *newnode zslInsert(zsl,newscore,x-ele);/* We reused the old node x-ele SDS string, free the node now* since zslInsert created a new one. */x-ele NULL;//删除节点释放内存zslFreeNode(x);return newnode; }跳表实例分析 查找 假设我们查找值为51的节点查找步骤如下。 从第2层开始1节点比51节点小向后比较21节点比51节点小继续向后比较。第2层21节点的next指向null所以下降一层第1层中21节点的next为41节点继续向后比较。41节点的next为61节点大于51节点所以下降一层继续向后比较第0层中51节点为要查询的节点返回结果 总结通过将有序链表分层由最上层依次向后查询如果本层的当前节点的next节点刚好大于要找的值或指向null则从当前节点开始下降一层向后查找依次类推如果找到则返回节点否则返回空。因此在节点较多的时候可以跳过一些节点查询效率大大提升这就是跳跃表的基本思想。 插入 跳跃表插入节点的4个步骤 第一步查找要插入的位置 跳表如下图所示。长度为3高度为2。若要插入节点31高度为3。 插入节点代码如下所示 x zsl-header; for (i zsl-level - 1; i 0; i--) {rank[i] i (zsl-level - 1) ? 0 : rank[i 1];while (x-level[i].forward (x-level[i].forward-score score ||(x-level[i].forward-score score sdscmp(x-level[i].forward-ele,ele) 0))){rank[i] x-level[i].span;x x-level[i].forward;}update[i] x; }update[] : 插入节点时需要更新被插入节点每层的前一个节点。由于每层更新的节点不同所以需要将更新的节点记录在update[i]中。rank[] : 记录当前层从header节点到update[i]节点所经历的步长在更新update[i]的span和设置新插入节点的span时会用到。查找节点score31, level 3)的插入位置逻辑如下 第一次for循环i 1。 x为跳跃表的头节点。此时i的值与zsl-level-1相等所以rank[1]的值为0。header-level[1].forward存在并且header-level[1].forward-score1小于31所以可以进入while循环rank[1] 1x为第一个节点第一个节点的第一层的forward指向null所以不会再进入while。经过第一次for循环rank[1]1。x和update[1]都为第一个节点(score1)。经过第二次for循环i0。x为跳跃表的第一个节点(score1)此时i的值与zsl-level-1不相等所以rank[0]等于rank[1]的值值为1x-level[0]-forward存在并且x-level[0].foreard-score21小于31所以进入while循环rank[0]2。x为第二个节点score21x-level[0]-forward存在并且x-level[0].foreard-score41大于31所以不会进入while经过第二次循环rank[0]2。x和update[0]都为第二个节点score21) update和rank复制后的跳跃表如下图所示。 第二步调整跳跃表高度 由第一步可知插入节点高度为3大于跳跃表高度2因此需要调整跳表高度。 level zslRandomLevel() for (i zsl-level; i level; i ) {rank[i] 0;update[i] zsl-header;update[i]-level[i].span zsl-length; } zsl-level level; 此时i的值为2level值为3所以只能进入一次for循环。由于header的第0层到第1层的forward都已经指向相应的节点而新添加的节点高度大于跳表的原高度所以第2层只需要更新header节点即可。rank用来更新span的变量其值是头节点到update[i]的节点数此次修改的是头节点所以rank[2]为0update[2]一定为头节点。update[2]-level[2].span的值先赋值为跳跃表的总长度后续在计算新插入节点level[2]的span时会用到此值。调整高度后跳表如下图所示 第三步插入节点 当update和rank都已赋值且节点都创建好后便可以插入节点。 x zslCreateNode(level, score, ele); for (i 0; i level; i) {x-level[i].forward update[i]-level[i].forward;update[i]-level[i].forward x;x-level[i].span update[i]-level[i].span - (rank[0] - rank[i]);update[i]-level[i].span (rank[0] - rank[i]) 1; } level值为3所以可以执行3次for循环过程如下。 第一次for循环 1.1 x的leve[0]的forward为update[0]的level[0]的forward节点即x-level[0].forward为score41的节点 1.2 update[0]的level[0]的下一个节点为新插入的节点 1.3 rank[0]-rank[0]0,update[0]-level[0].span1,所以x-level[0].span1。 1.4 update[0]-level[0].span 0 1 1 插入节点并更新0层后跳表如下所示 第2次for循环 2.1 x的level[1]的forward为update[1]的level[1]的forward节点即x-level[1].forward为null。 2.2 update[1]的level[1]的下一个节点为新插入的节点 2.3 rank[0]-rank[1] 1,update[1]-level[1].span2,所以x-level[1].span1 2.4 update[1]-level[1].span112 插入节点并更新第1层后的跳表如下所示。 第3次for循环 3.1 x的level[2]的forward为update[2]的level[2]的forward节点即x-level[2].forward为null。 3.2 update[2]的level[2]的下一个节点为新插入的节点。 3.3 rank[0]-rank[2]2因为update[2]-level[2].span3所以x-level[2].span1 3.4 update[2]-level[2].span213 插入节点并更新第2层后的跳表如下所示。 第四步调整backward 根据update的赋值过程新插入节点的前一个节点一定是update[0]由于每个节点的后退指针只有一个于此节点的层数无关所以当插入节点不是最后一个节点时需要更新被插入节点的backward指向update[0]。如果新插入节点是最后一个节点则需要更新跳表的尾节点为插入新节点。插入节点后更新跳表长度1.插入新节点后的跳表如下所示。 epoll多路复用IO机制 看这篇就够了
http://www.zqtcl.cn/news/146150/

相关文章:

  • 公司网页是什么被公司优化掉是什么意思
  • 酒店网站建设方案结束语慈溪企业排名网站
  • 做行业网站广告能赚多少钱百度搜索下载安装
  • 寺院网站建设网页搭建
  • 网站设计报价是多少wordpress登录接口
  • 灵宝网站建设建h5网站费用
  • 泊头做网站的有哪些深圳网页制作与网站建设服务器
  • 网站设计的思路网页无法访问百度
  • 简述你对于网站建设的认识网络工程就业岗位有哪些
  • 征婚网站上教人做恒指期货做网站颜色黑色代码多少
  • 海南省建设工程质量监督网站如何做搞笑原创视频网站
  • 网页游戏人气排行榜百度seo插件
  • 免费申请论坛网站更改域名代理商对网站有影响吗
  • 河南做网站公司报价工商做年报网站
  • 用狐狸做logo的网站现在网站开发技术有哪些
  • html 网站添加悬浮二维码瑜伽网站设计
  • 帮别人做网站的单子制作图片库
  • 网站注册步骤律师在线咨询免费24小时电话
  • 经典的网站设计工具怎么做网站表格
  • 韩文网站建设wordpress 置顶顺序
  • 做网站好还是做app好做房产的网站排名
  • 纯静态网站部署服务器如何做高端网站建设
  • 特色食品网站建设策划书网站建设丶seo优化
  • 安徽省六安市建设局网站网络服务提供者知道网络用户利用其网络服务侵害
  • 珠海建设局网站东莞市建设信息网
  • 已有域名怎么做网站wordpress二维码制作教程
  • 做招生网站网站织梦后台一片白
  • wordpress 表单录入优化网站的技巧
  • 域名注册网站的域名哪里来的信息型网站
  • 商贸网站建设常见的网站结构有哪些