redis源码分析

是什么

是基于内存(而不是磁盘)的kv(而不是关系型mysql那种)数据库,通过空间换时间

源码分析

跳表skiplist

假设你有个有序链表,你想看某个特定的值是否出现在这个链表中,那你是不是只能遍历一次链表才能知道,时间复杂度为O(n)。
在这里插入图片描述

可能有人会问为什么不直接用连续存储,我们还能用二分查找,用链表是想继续保留它修改时间复杂度低的优势。那我们如何优化单次查找的速度?其实思路很像是二分查找,但单链表无法随机访问的特性限制了我们,但二分逐渐缩小范围的思路启发了我们,能不能想什么办法逐渐缩小范围?

我是不是可以在原链表之上新建一个链表,新链表是原链表每隔一个节点取一个。假设原链表为L0,新链表为L1,L1中的元素是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数组中,例如在level=3那层,权值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[i+1];
        //在当前层与当前节点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]=0,update[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;
    else
        zsl->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 (level<ZSKIPLIST_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的节点,查找步骤如下。
在这里插入图片描述

  1. 从第2层开始,1节点比51节点小,向后比较
  2. 21节点比51节点小,继续向后比较。第2层21节点的next指向null,所以下降一层
  3. 第1层中,21节点的next为41节点,继续向后比较。41节点的next为61节点,大于51节点,所以下降一层继续向后比较
  4. 第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时会用到。
  • 查找节点(score=31, level = 3)的插入位置,逻辑如下:
  1. 第一次for循环,i = 1。 x为跳跃表的头节点。
  2. 此时i的值与zsl->level-1相等,所以rank[1]的值为0。
  3. header->level[1].forward存在,并且header->level[1].forward->score==1小于31,所以可以进入while循环,rank[1] = 1,x为第一个节点
  4. 第一个节点的第一层的forward指向null,所以不会再进入while。经过第一次for循环,rank[1]=1。x和update[1]都为第一个节点(score=1)。
  5. 经过第二次for循环,i=0。x为跳跃表的第一个节点(score=1)
  6. 此时i的值与zsl->level-1不相等,所以rank[0]等于rank[1]的值,值为1
  7. x->level[0]->forward存在,并且x->level[0].foreard->score==21小于31,所以进入while循环,rank[0]==2。x为第二个节点(score=21)
  8. x->level[0]->forward存在,并且x->level[0].foreard->score==41大于31,所以不会进入while,经过第二次循环,rank[0]=2。x和update[0]都为第二个节点(score=21)

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的值为2,level值为3,所以只能进入一次for循环。由于header的第0层到第1层的forward都已经指向相应的节点,而新添加的节点高度大于跳表的原高度,所以第2层只需要更新header节点即可。rank用来更新span的变量,其值是头节点到update[i]的节点数,此次修改的是头节点,所以rank[2]为0,update[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循环,过程如下。
  1. 第一次for循环
    1.1 x的leve[0]的forward为update[0]的level[0]的forward节点,即x->level[0].forward为score=41的节点
    1.2 update[0]的level[0]的下一个节点为新插入的节点
    1.3 rank[0]-rank[0]=0,update[0]->level[0].span=1,所以x->level[0].span=1。
    1.4 update[0]->level[0].span = 0 + 1 = 1
  • 插入节点并更新0层后,跳表如下所示
    在这里插入图片描述
  1. 第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].span=2,所以x->level[1].span=1
    2.4 update[1]->level[1].span=1+1=2
  • 插入节点并更新第1层后的跳表如下所示。
    在这里插入图片描述
  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].span=3,所以x->level[2].span=1
    3.4 update[2]->level[2].span=2+1=3
  • 插入节点并更新第2层后的跳表如下所示。
    在这里插入图片描述
第四步:调整backward
  • 根据update的赋值过程,新插入节点的前一个节点一定是update[0],由于每个节点的后退指针只有一个,于此节点的层数无关,所以当插入节点不是最后一个节点时,需要更新被插入节点的backward指向update[0]。如果新插入节点是最后一个节点,则需要更新跳表的尾节点为插入新节点。插入节点后,更新跳表长度+1.
  • 插入新节点后的跳表如下所示。
    在这里插入图片描述

epoll多路复用IO机制

看这篇就够了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/449181.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何搭建财务数据运营体系:基于财务五力模型的分析

在当今复杂多变的商业环境中,财务数据作为企业决策的重要参考依据,其运营体系的搭建显得尤为关键。一个健全、高效的财务数据运营体系不仅能够为企业提供准确的财务数据支持,还能帮助企业在激烈的市场竞争中保持领先地位。基于财务五力模型的分析,我们可以从收益力、安定力…

windows server 2019 服务器配置的方法步骤

一、启用远程功能二、测试三、解决多用户登录的问题 一、启用远程功能 右键点击【此电脑】–【属性】&#xff0c;进入“【控制面板\系统和安全\系统】”&#xff0c;点击-【远程设置】(计算机找不到就使用【winE】快捷键) 2、在“远程桌面”下方&#xff0c;点击【允许远程连…

NOIP2018-S-DAY1-3-赛道修建(洛谷P5021)的题解

目录 题目 原题描述&#xff1a; 题目描述 输入格式 输出格式 输入输出样例 主要思路&#xff1a; check&#xff1a; 真正的code: 原题描述&#xff1a; 题目描述 C 城将要举办一系列的赛车比赛。在比赛前&#xff0c;需要在城内修建 条赛道。 C 城一共有 个路…

gitee分支管理,合并冲突

1、gitee展示分支 git branch 2、展示远程分支 git branch -r 3、新建分支 git branch base 4、切换分支 git checkout base 合并冲突 当代码在服务器上被提交了&#xff0c;再在本地提交会提示报错 点击merge

从GPT入门,到R语言基础与作图、回归模型分析、混合效应模型、多元统计分析及结构方程模型、Meta分析、随机森林模型及贝叶斯回归分析综合应用等专题及实战案例

目录 专题一 GPT及大语言模型简介及使用入门 专题二 GPT与R语言基础与作图&#xff08;ggplot2&#xff09; 专题三 GPT与R语言回归模型&#xff08;lm&glm&#xff09; 专题四 GPT与混合效应模型&#xff08;lmm&glmm&#xff09; 专题五 GPT与多元统计分析&…

中国社会科学院与美国杜兰大学金融管理硕士——二月二,抬头皆是惊喜

在繁忙的都市生活中&#xff0c;每个人都在为自己的未来打拼&#xff0c;寻找着属于自己的那片天空。二月二&#xff0c;龙抬头&#xff0c;象征着春天的到来&#xff0c;万物复苏。在这个特殊的日子里&#xff0c;对于那些追求学术与职业双重成就的人来说&#xff0c;&#xf…

【Java常用API】正则表达式的基础使用

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

zabbix5监控tomcat

zabbix tomcat客户端配置 1、配置tomcat catalina.sh文件 CATALINA_OPTS"$CATALINA_OPTS -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port12345 -Dcom.sun.management.jmxremote.authenticatefalse -Dcom.sun.management.jmxremote.sslfalse -Djav…

1.Python是什么?——跟老吕学Python编程

1.Python是什么&#xff1f;——跟老吕学Python编程 Python是一种什么样的语言&#xff1f;Python的优点Python的缺点 Python发展历史Python的起源Python版本发展史 Python的价值学Python可以做什么职业&#xff1f;Python可以做什么应用&#xff1f; Python是一种什么样的语言…

WPF —— TextBlock、LineBreak RadioButton控件详解

一:TextBlock 1&#xff1a;TextBlock 简介 <LineBreak/> 换行 显示文本 标签内容和content属性共存 2、TextBlock 常用的属性 Foreground&#xff1a;TextBlock的文本内容的颜色。 Background&#xff1a;背景&#xff0c;获取或设置要用于填充内容区域背景的 Brush…

VMware 集群-虚拟机配置反亲和性(互斥)

简介 博客&#xff1a;https://songxwn.com/ 为实现应用系统的冗余&#xff0c;经常会双机或者多机部署&#xff08;如数据库集群等&#xff09;。在VMware 集群里面&#xff0c;要保证不同应用集群的节点虚拟机在不同的物理宿主机上&#xff0c;防止单个宿主机故障&#xff…

EasyNVR级联EasyCVR后,EasyCVR播放视频导致EasyNVR崩溃是什么原因?

视频综合管理平台EasyCVR视频监控系统支持多协议接入、兼容多类型设备&#xff0c;平台可以将监控区域内所有部署的监控设备进行统一接入与集中汇聚管理&#xff0c;实现对监控区域的实时视频监控、录像与存储、设备管理、云台控制、语音对讲、级联共享等&#xff0c;在监控中心…

指纹挂锁方案——采用ACH512或ACM32FP4指纹芯片和88*112传感器,指纹识别速度快,BOM成本低

方案概述 指纹挂锁方案采用ACH512或ACM32FP4指纹芯片和88*112传感器&#xff0c;指纹识别速度快&#xff0c;BOM成本低&#xff0c;非常适合挂锁、内门锁、箱包锁、箱柜锁等场景。 方案特点 • 主控算法单芯片&#xff1a;ACH512或ACM32FP4 • 传感器分辨率&#xff1a;88*11…

探究精酿啤酒的秘密:原料中的天然酵母与纯净水质

在啤酒的世界中&#xff0c;Fendi Club精酿啤酒以其与众不同的口感和深远的余味吸引了全球的啤酒爱好者。而这一切&#xff0c;都归功于其选用的上好原料&#xff0c;特别是天然酵母和纯净水质。 天然酵母是啤酒的灵魂。与工业生产的啤酒酵母不同&#xff0c;天然酵母富含丰富的…

跨境账号养号怎么做?Facebook、亚马逊运营必看

之前我们讨论过很多关于代理器的问题。它们的工作原理是什么?在不同的软件中要使用那些代理服务器?这些代理服务器之间的区别是什么?什么是反检测浏览器等等。 除了这些问题&#xff0c;相信很多人也会关心在使用不同平台的时代理器的选择问题。比如&#xff0c;为什么最好…

使用helm部署clickhouse

&#xff08;作者&#xff1a;陈玓玏&#xff09; 前置条件 已安装 Kubernetes 集群&#xff1b; 已安装 Helm 包管理工具。 部署 1 添加 RadonDB ClickHouse 的 Helm 仓库 helm repo add ck https://radondb.github.io/radondb-clickhouse-kubernetes/ helm repo upd…

精品基于Springboot的聊天交友系统的设计与实现

《[含文档PPT源码等]精品基于Springboot的聊天交友系统的设计与实现[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; Java——涉及技术&#xff1a; 前端使用技术&#xf…

js实现导出/下载excel文件

js实现导出/下载excel文件 // response 为导出接口返回数据&#xff0c;如上图 const exportExcel (response, fileName:string) >{const blob new Blob([response.data], {type: response.headers[content-type] //使用获取的excel格式});const downloadElement documen…

Java 三大并大特性-有序性介绍(结合代码、分析源码)

目录 一、概念解析 二、 有序性代码例子 2.1 代码 2.2 执行结果 三、 指令重排序机制 3.1 为什么要引入指令重排序 3.2 指令重排序的分类 3.2.1 编译器优化重排序 3.2.2 指令级并行的重排序 3.2.3 内存系统的重排 3.3 指令重排序规范 3.3.1 as-if-serial 规范 3.3…

基于boost库的搜索引擎项目

文章目录 一、项目背景二、什么样的搜索引擎三、搜索引擎的宏观图原理四、Parse模块4.1下载boost库源代码4.2提取boost库中以.html为结尾的文件4.2.1 boost库的简单使用 4.3数据清洗(去标签化)4.3.1数据清洗的具体实现 4.4将清洗后的数据写入到raw.txt文件中 五、正排索引 vs 倒…
最新文章