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

redis笔记-数据结构

zset

zset一方面它是一个 set,保证了内部value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。

zset的底层是由字典和跳表实现。

字典主要用来存储value和score的对应关系。跳表这个数据结构主要用来提供zset按照score排序的功能、指定score范围获取value列表的功能。

struct zset {dict *dict; // all values   value=>scorezskiplist *zsl;  
}

跳表

跳表数据结构包含的属性:

  1. header:表示kv head的地址
  2. maxLevel:最高层级
struct zskiplist {zslnode* header; // 跳跃列表头指针int maxLevel; // 跳跃列表当前的最高层map<string, zslnode*> ht; // hash 结构的所有键值对  
}

以下就是跳表的示意图:

每一个kv按照score有序排列的,不同的 kv 层高可能不一样,层数越高的 kv 越少。也就是不同 kv 的level[ ]长度不一样。同一层的 kv 会使用指针串起来。每一个层元素的遍历都是从 kv header 出发。

为什么需要这么设计呢?如果只有一层,也就是一个双链表,所有元素按照score排序,我们如果想要快速定位到某个score,我们就需要从头指针依次遍历,时间复杂度为O(n)。如果我们设计成跳表这个多层结构,效果类似于二分查找,时间复杂度为O(lg(n))。

在这里插入图片描述

每一个结点包含的属性如下:

struct zslnode {string value;double score;zslforward*[] level; // 多层连接指针zslnode* backward; // 回溯指针
}
struct zslforward {zslnode* forward;long span; // 跨度
}

查找

如果当前节点的下一个节点包含的值比目标元素值小,则继续向右查找。如果下一个节点的值比目标值大,就转到当前层的下一层去查找。

在这里插入图片描述

比如说要查找21,当查找到6这个结点时,6比21小,继续向右查找,因为此时在6结点level[3],level[3]的下一个结点是nil,不符合,那么就转到6的level[2],level[2]的下一个结点是(6->level[2].forward)25,比目标值大,继续转到6的level[1],level[1]的下一个结点是9,9比21小,继续向右查找……

注意: zset 的排序元素不只看 score 值,如果score 值相同还需要再比较 value 值 (字符串比较)。为了防止如果每个元素的score值都相同的话,查找时间复杂度变成O(n)。

插入

当客户端使用 zset add 命令时,redis底层插入元素的过程:

在这里插入图片描述

zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) {// 存储搜索路径zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;// 存储经过的节点跨度unsigned int rank[ZSKIPLIST_MAXLEVEL];int i, level;serverAssert(!isnan(score));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 &&compareStringObjects(x->level[i].forward->obj,obj) < 0))) {// 累计搜索路径中经过各结点的跨度。也就是最后查询到的元素的rank值。rank[i] += x->level[i].span;x = x->level[i].forward;}update[i] = x;}/**正式进入插入过程**/// 随机一个层数level = zslRandomLevel();if (level > zsl->level) {for (i = zsl->level; i < level; i++) {rank[i] = 0;update[i] = zsl->header;update[i]->level[i].span = zsl->length;}zsl->level = level;}// 创建新节点x = zslCreateNode(level,score,obj);// 重排一下前向指针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;}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;
}
  • 通过 kv head 这个结点获得最高层的结点地址。
  • 逐层降级寻找目标结点,所经过的结点存储在搜索路径update[]数组中。
  • 创建一个新结点。
  • 为这个结点算一个随机层数,表示这个结点的level[]最大是第几层。
  • 遍历update数组,为这个新创建的结点的每层level设置好前后指针。

删除

删除过程和插入过程类似,都需先把这个「搜索路径」找出来。然后对于每个层的相关节点都重排一下前向后向指针就可以了。同时还要注意更新一下最高层数 maxLevel。

更新

当我们调用 zadd 方法时,如果对应的 value 不存在,那就是【插入过程】。

如果这个value 已经存在了,只是调整一下 score 的值,

  1. 假设这个新的score 值不会带来排序位置上的改变,那么就不需要调整位置,直接修改元素的 score 值就可以了。
  2. 如果新的score值让排序位置改变了,那就要调整位置。如何调整位置?直接将这个元素删了,然后插入。这一点可以优化,因为如果直接删,然后再插入,需要两次路径搜索,对于改变score值后仍不需要调整位置的情况就可以优化。

字典

dict数据结构可以应用在:

  • hash 结构的数据
  • 整个 Redis 数据库的所有 key 和 value
  • 带过期时间的 key 集合
  • zset 集合中存储 value 和 score 值的映射关系
struct RedisDb {dict* dict; // all keys key=>valuedict* expires; // all expired keys key=>long(timestamp)
}
struct zset {dict *dict; // all values value=>scorezskiplist *zsl;
}

dict数据结构包含的属性:

struct dict {...dictht ht[2]; // 两个dictht结构,也就是两个hashtable
}
struct dictht {dictEntry** table; // 二维,第一维是数组,第二维是链表。数组中存储的是第二维链表的第一个元素的指针。long size; // 第一维数组的长度long used; // hash 表中的元素个数...
}
struct dictEntry {void* key;void* val;dictEntry* next; // 链接下一个 entry
}

rehash

rehash条件:

Dict在每次新增键值对时都会检查负载因子(LoadFactor = used/size) ,满足以下两种情况时会触发哈希表扩容
哈希表的 LoadFactor >= 1,并且服务器没有执行 BGSAVE 或者 BGREWRITEAOF 等后台进程;
哈希表的 LoadFactor > 5 ;

rehash过程:(渐进式rehash)

  • 计算新hash表的realeSize,值取决于当前要做的是扩容还是收缩:

    • 如果是扩容,则新size为第一个大于等于dict.ht[0].used + 1的2^n
    • 如果是收缩,则新size为第一个大于等于dict.ht[0].used的2^n (不得小于4)
  • 按照新的realeSize申请内存空间,创建dictht,并赋值给dict.ht[1]

  • 设置dict.rehashidx = 0,标示开始rehash

  • 将dict.ht[0]中的每一个dictEntry都rehash到dict.ht[1](注意,这里不是一次性就将所有entry移到ht[1]中。)

    每次执行新增、查询、修改、删除操作时,都检查一下dict.rehashidx是否大于-1,如果是则将dict.ht[0].table[rehashidx]的entry链表rehash到dict.ht[1],并且将rehashidx++。直至dict.ht[0]的所有数据都rehash到dict.ht[1]。—渐进式哈希

  • 将dict.ht[1]赋值给dict.ht[0],给dict.ht[1]初始化为空哈希表,释放原来的dict.ht[0]的内存。

  • 将rehashidx赋值为-1,代表rehash结束。

  • 在rehash过程中,新增操作,则直接写入ht[1],查询、修改和删除则会在dict.ht[0]和dict.ht[1]依次查找并执行。这样可以确保ht[0]的数据只减不增,随着rehash最终为空。

rehash时机:

rehash将ht[0]的数据搬迁到ht[1]是在客户端执行命令中时发生的。也就是渐进性哈希。如果客户端闲下来,redis则通过定时任务对字典进行搬迁。

string

Redis 中的字符串是可以修改的字符串,在内存中它是以字节数组的形式存在的。这个字符串叫「SDS」,也就是 Simple Dynamic String。它的结构是一个带长度信息的字节数组。

SDS

struct SDS<T> {T capacity; // 数组容量T len; // 数组长度byte flags; // 特殊标识位,不理睬它byte[] content; // 数组内容
}

在这里插入图片描述

如上图所示,capacity 表示所分配数组的长度,len 表示字符串的实际长度。

embstr vs raw

Redis 的字符串有两种存储方式,在长度特别短时,使用 emb 形式存储 (embeded),当长度超过 44 时,使用 raw 形式存储。

所有redis对象都有下面这个结构头,包括我们现在研究的SDS对象也会有这个对象头。

struct RedisObject {int4 type; // 4bits   不同的对象具有不同的类型int4 encoding; // 4bits   同一个类型的 type 会有不同的存储形式encoding(4bit),int24 lru; // 24bits   int32 refcount; // 4bytes   每个对象都有个引用计数,当引用计数为零时,对象就会被销毁,内存被回收。void *ptr; // 8bytes,64-bit system   ptr 指针将指向对象内容 (body) 的具体存储位置。
} robj;

这样一个 RedisObject 对象头需要占据 16 字节的存储空间。

我们再看 SDS 结构体的大小:

struct SDS {int8 capacity; // 1byteint8 len; // 1byteint8 flags; // 1bytebyte[] content; // 内联数组,长度为 capacity
}

一个SDS对象头至少是3字节,那么一整个SDS对象至少是3+16=19字节,也就是说分配一个字符串最小空间占用为19字节。

在这里插入图片描述

  • embstr 存储形式将 RedisObject 对象头和 SDS 对象连续存在一起,使用 malloc 方法一次分配。
  • raw 存储形式需要两次malloc,两个对象头在内存地址上一般是不连续的。

内存分配器 jemalloc/tcmalloc 等分配内存大小的单位都是 2、4、8、16、32、64 等等,为了能容纳一个完整的 embstr 对象,**jemalloc 最少会分配 32 字节的空间,如果字符串再稍微长一点,那就是 64 字节的空间。**如果总体超出了 64 字节,Redis 认为它是一个大字符串,不再使用 emdstr 形式存储,而该用 raw 形式。

也就是如果分配的是64字节的空间,那么扣掉SDS对象头和redis对象头,剩下64 - 19 = 45字节给字符串,而字符串是以字节\0 结尾的,因此字符串长度最大为44字节,如果超过44字节,那么用raw形式存储。

oc/tcmalloc 等分配内存大小的单位都是 2、4、8、16、32、64 等等,为了能容纳一个完整的 embstr 对象,**jemalloc 最少会分配 32 字节的空间,如果字符串再稍微长一点,那就是 64 字节的空间。**如果总体超出了 64 字节,Redis 认为它是一个大字符串,不再使用 emdstr 形式存储,而该用 raw 形式。

也就是如果分配的是64字节的空间,那么扣掉SDS对象头和redis对象头,剩下64 - 19 = 45字节给字符串,而字符串是以字节\0 结尾的,因此字符串长度最大为44字节,如果超过44字节,那么用raw形式存储。

参考

《Redis深度历险:核心原理和应用实践》
https://www.jianshu.com/p/09c3b0835ba6

相关文章:

redis笔记-数据结构

zset zset一方面它是一个 set&#xff0c;保证了内部value 的唯一性&#xff0c;另一方面它可以给每个 value 赋予一个 score&#xff0c;代表这个 value 的排序权重。 zset的底层是由字典和跳表实现。 字典主要用来存储value和score的对应关系。跳表这个数据结构主要用来提…...

webpack的常见配置

Webpack 是一个现代 JavaScript 应用的模块打包工具&#xff0c;用于将项目中的多个文件和依赖打包成浏览器可以识别的文件&#xff0c;通常是一个或多个 JavaScript、CSS 或其他静态资源的 bundle&#xff08;将多个模块或文件合并成一个或几个文件的过程&#xff0c;这些合并…...

text-embedding-ada-002;BGE模型;M3E模型是Moka Massive Mixed Embedding;BERT

目录 text-embedding-ada-002 一、模型概述 二、模型功能 三、模型特点 四、模型应用 五、模型优势 BGE模型 一、模型背景与特点 二、模型性能与表现 三、模型迭代与发展 M3E模型是Moka Massive Mixed Embedding 一、基本信息 二、技术特点 三、应用场景 四、性能…...

WebRTC 环境搭建

主题 本文主要描述webrtc开发过程中所需的环境搭建 环境&#xff1a; 运行环境&#xff1a;ubuntu 20.04 Node.js环境搭建 安装编译 Node.js 所需的依赖包: sudo apt-get update sudo apt-get install -y build-essential libssl-dev 下载 Node.js 源码: curl -sL htt…...

FastHTML快速入门:http方法,CSS文件和内联样式,其他静态媒体文件位置

HTTP方法 FastHTML通过函数名与HTTP方法进行匹配。到目前为止&#xff0c;我们定义的URL路由都是针对HTTP GET方法的&#xff0c;这是网页最常见的方法。 表单提交通常作为HTTP POST发送。在处理更动态的网页设计时&#xff0c;也就是所谓的单页应用&#xff08;SPA&#xff0…...

项目管理和研发管理中的痛点及其解决方案

在现代企业中&#xff0c;研发管理和项目管理面临着多重挑战&#xff0c;包括资源配置不当、沟通不畅、目标不明确、进度控制困难等。这些痛点不仅影响项目的顺利推进&#xff0c;还可能导致企业在市场竞争中处于劣势。尤其是在资源配置不当方面&#xff0c;企业往往难以合理分…...

机器学习(基础1)

数据集 sklearn玩具数据集 数据量小&#xff0c;数据在sklearn库的本地&#xff0c;只要安装了sklearn&#xff0c;不用上网就可以获取 sklearn现实世界数据集 数据量大&#xff0c;数据只能通过网络获取&#xff08;为国外数据集&#xff0c;下载需要梯子&#xff09; skle…...

我谈维纳(Wiener)复原滤波器

Rafael Gonzalez的《数字图像处理》中&#xff0c;图像复原这章内容几乎全错。上篇谈了图像去噪&#xff0c;这篇谈图像复原。 图像复原也称为盲解卷积&#xff0c;不处理点扩散函数&#xff08;光学传递函数&#xff09;的都不是图像复原。几何校正不属于图像复原&#xff0c…...

怎么看真假国企啊?怎么识别假冒国企的千层套路?

一、怎么看真假国企啊&#xff1f; 1.使用具有迷惑性的名称&#xff1a;假冒国企往往在名称中使用“中国”、“中”、“国”等字样&#xff0c;或与知名国企名称相似的字号&#xff0c;以增加其可信度。 2.注册资本虚高&#xff1a;为了显示实力&#xff0c;假冒国企可能会在…...

C#中break和continue的区别?

在C#编程语言中&#xff0c;break和continue是两个用于控制循环流程的关键字&#xff0c;但它们的作用和用途有所不同。 break关键字 break关键字用于立即终止它所在的最内层循环或switch语句&#xff0c;并跳出该循环或switch块。程序执行将继续进行循环或switch语句之后的下一…...

Linux部署nginx访问文件403

问题描述&#xff1a;在linux服务器上通过nginx部署&#xff0c;访问文件403 新配置了一个用户来部署服务&#xff0c;将部署文件更新到原有目录下&#xff0c;结果nginx访问403 原因&#xff1a;没有配置文件的读写权限&#xff0c;默认不可读写&#xff0c;nginx无法访问到文…...

华为OD机试 - 数字排列 - 深度优先搜索dfs算法(Python/JS/C/C++ 2024 C卷 200分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…...

Scrapy爬取heima论坛所有页面内容并保存到数据库中

前期准备&#xff1a; Scrapy入门_win10安装scrapy-CSDN博客 新建 Scrapy项目 scrapy startproject mySpider03 # 项目名为mySpider03 进入到spiders目录 cd mySpider03/mySpider03/spiders 创建爬虫 scrapy genspider heima bbs.itheima.com # 爬虫名为heima &#…...

Kafka参数了解

Kafka配置参数完整说明 1. 基础配置 参数名说明推荐值参考值broker.idbroker的唯一标识符每个节点唯一的整数1delete.topic.enable是否允许删除topictruetruelistenersbroker监听地址SASL_PLAINTEXT://host:9092SASL_PLAINTEXT://172.24.77.15:9092advertised.listeners对外发…...

sql专题 之 where和join on

文章目录 前言where介绍使用过滤结果集关联两个表 连接外连接内连接自然连接 使用inner join和直接使用where关联两个表的区别总结 前言 从数据库查询数据时&#xff0c;一张表不足以查询到我们想要的数据&#xff0c;更多的时候我们需要联表查询。 联表查询我们一般会使用连接…...

day12:版本控制器

版本控制 使用到的命令&#xff1a; ls -al查看当前目录下的文件及文件夹mkdir新建目录rm -rf递归强制删除文件夹 一、安装配置 1、下载地址 Git 2、初始配置 #用户名 git config --global user.name "自定义用户名" #邮箱&#xff08;公司的联系方式--追责&…...

第四十一章 Vue之初识VueX

目录 一、引言 1.1. vuex的概念 1.2. vuex使用场景 1.3. 优势 二、创建演示项目 2.1. 构建项目步骤 2.2. 项目最终生成结构 2.3. 创建项目文件 2.3.1. App.vue 2.3.2. Son1.vue 2.3.3. Son2.vue 三、创建一个空仓库 3.1. 安装vuex 3.2. 新建仓库 3.3. 挂载仓库…...

GIT的基本使用与进阶

GIT的简单入门 一.什么是git&#xff1f; Git 是一个开源的分布式版本控制系统&#xff0c;用于跟踪文件更改、管理代码版本以及协作开发。它主要由 Linus Torvalds 于 2005 年创建&#xff0c;最初是为 Linux 内核开发而设计的。如今&#xff0c;Git 已经成为现代软件开发中…...

【Linux系统】—— 基本指令(二)

【Linux系统】—— 基本指令&#xff08;二&#xff09; 1 「alias」命令1.1 「ll」命令1.2 「alias」命令 2 「rmdir」指令与「rm」指令2.1 「rmdir」2.2 「rm」2.2.1 「rm」 删除普通文件2.2.2 「rm」 删除目录2.2.3 『 * 』 通配符 3 「man」 指令4 「cp」 指令4.1 拷贝普通…...

MFC工控项目实例三十实现一个简单的流程

启动按钮夹紧 密闭&#xff0c;时间0到平衡 进气&#xff0c;时间1到进气关&#xff0c;时间2到平衡关 检测&#xff0c;时间3到平衡 排气&#xff0c;时间4到夹紧开、密闭开、排气关。 相关代码 void CSEAL_PRESSUREDlg::OnTimer_2(UINT nIDEvent_2) {// if (nIDEvent_21 &am…...

【Android、IOS、Flutter、鸿蒙、ReactNative 】文本点击事件

Android Studio 版本 Android Java TextView 实现 点击事件 参考 import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.TextView; import android.widget.Toast;public c…...

json转excel,读取json文件写入到excel中【rust语言】

一、rust代码 将json文件写入到 excel中。&#xff08;保持json &#xff1a;key原始顺序&#xff09; use indexmap::IndexMap; use serde::Deserialize; use serde_json::{Value, from_str}; use std::error::Error; use std::io::{self, Write}; use std::path::{Path}; u…...

Java面试要点06 - static关键字、静态属性与静态方法

本文目录 一、引言二、静态属性&#xff08;Static Fields&#xff09;三、静态方法&#xff08;Static Methods&#xff09;四、静态代码块&#xff08;Static Blocks&#xff09;五、静态内部类&#xff08;Static Nested Classes&#xff09;六、静态导入&#xff08;Static…...

动态规划-背包问题——416.分割等和子集

1.题目解析 题目来源 416.分割等和子集——力扣 测试用例 2.算法原理 1.状态表示 这里背包问题基本上和母题的思路大相径庭&#xff0c;母题请见 [模板]01.背包 &#xff0c;这里的状态表示与装满背包的情况类似&#xff0c;第二个下标就是当选择的物品体积直接等于j时是否可…...

Pr:视频过渡快速参考(合集 · 2025版)

Adobe Premiere Pro 自带七组约四十多个视频过渡 Video Transitions效果&#xff0c;包含不同风格和用途&#xff0c;可在两个剪辑之间创造平滑、自然的转场&#xff0c;用来丰富时间、地点或情绪的变化。恰当地应用过渡可让观众更好地理解故事或人物。 提示&#xff1a; 点击下…...

网络安全---安全见闻2

网络安全—安全见闻 拓宽视野不仅能够丰富我们的知识体系&#xff0c;也是自我提升和深造学习的重要途径&#xff01;&#xff01;&#xff01; 设备漏洞问题 操作系统漏洞 渗透测试视角&#xff1a;硬件设备上的操作系统可能存在各种漏洞&#xff0c;攻击者可以利用这些漏洞…...

解决因为TortoiseSVN未安装cmmand line client tools组件,导致idea无法使用svn更新、提交代码

一.错误信息 1.更新代码时&#xff1a;SVN: 更新错误 找不到要更新的版本管理目录。 2.提交代码&#xff1a;检测不到任何更新&#xff08;实际上有代码修改&#xff09;。 3.Cannot run program "svn"。 二.原因分析 在电脑上新安装的的客户端TortoiseSVN、ide…...

Ubuntu 20.04安装CUDA 11.0、cuDNN 8.0.5

不知道咋弄的ubuntu20.04电脑的cuda驱动丢了&#xff0c;无奈需装PyTorch环境&#xff0c;只有CUDA11.0以上版本才支持Ubuntu20.04&#xff0c;所以安装了CUDA11.0、cuDNN8.0.5 为防止频繁在浏览器检索对应的贴子&#xff0c;今天记录一下。 一. 驱动安装 为防止驱动安装后没…...

鸿蒙 APP 发布上架

证书创建与打包: https://developer.huawei.com/consumer/cn/doc/app/agc-help-releaseharmony-0000001933963166 不同环境多渠道打包: //todo 备案相关 一、除了发布应用商店以外,还有3个渠道,都适合小规模内测。 【1】开放式测试:发给指定白名单用户 【2】发布企业内…...

【C++笔记】C++三大特性之继承

【C笔记】C三大特性之继承 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】C三大特性之继承前言一.继承的概念及定义1.1 继承的概念1.2继承的定义1.3继承基类成员访问方式的变化1.4继承类模板 二.基类和派生类间的转…...

浪起网站建设/seo排名赚app靠谱吗

#include "stdafx.h" #include <iostream> #include <string>using namespace std; void other() {static int a2;//静态局部变量&#xff0c;具有全局寿命&#xff0c;局部作用域&#xff0c;只在第一次进入函数时被初始化int c10;//局部变量&#xff0…...

内江如何做百度的网站/推广互联网营销

新加坡总理公署20日发布公告&#xff0c;宣布将于今年5月1日成立智慧国及数码政府工作团&#xff0c;该机构将隶属于总理公署&#xff0c;致力于推动该国“智慧国”建设。 公告说&#xff0c;智慧国及数码政府工作团将由一个部长级委员会领导&#xff0c;副总理兼国家安全统筹部…...

动态网站开发的前景/百度霸屏推广靠谱吗

报告类型&#xff1a;产业研究报告格式&#xff1a;电子版、纸介版、电子纸介出品单位&#xff1a;智研咨询官网链接&#xff1a;中国产业信息网 - 产业前景投资趋势门户-智研旗下产业信息咨询平台​www.chyxx.com报告链接&#xff1a;2021-2027年中国载货电梯行业市场调查研究…...

如何去门户网站做推广呢/杭州seo薪资水平

内容概要&#xff1a; 满足下面2个条件时&#xff0c; 1. 父类有虚函数&#xff0c;子类也有虚函数&#xff0c;且子类的虚函数重写或覆盖了父类的虚函数 2. 非虚继承 类对象之内存布局 在前面的例子中&#xff0c;恢复原来的两个虚函数vfBase_1()和vfBase_2()&#xff0c;同时…...

备案ip 查询网站查询网站查询系统/微信附近人推广引流

会议是浪费工作时间的最佳去处 今天你开了多少个会&#xff1f;这个星期呢&#xff1f;这个月呢&#xff1f;再自问一下&#xff0c;那些会议中有多少是值得参加的&#xff1f;如果把相同的时间用在工作上&#xff0c;你又能完成多少事情&#xff1f;我们究竟为什么要开会&…...

外贸网站设计方案/友链交易平台源码

简介 迄今为止我们见到的所有程序总是含有一连串语句并且python忠实的顺序执行它们。 那么如何改变它们的执行顺序呢&#xff1f;例如你希望程序根据不同情况作出不同反应&#xff0c;按照当前时间分别 打印出’Good Morning’或’Good Evening’? 也许你已经猜到了&#xff0…...