Redis经典五种数据类型底层实现原理解析

news2024/11/26 3:50:04

目录

    • 总纲
      • redis的k,v键值对
      • 新的三大类型
    • 五种经典数据类型
      • redisObject
        • 结构图示
        • 结构讲解
      • 数据类型与数据结构关系图示
      • string数据类型
        • 三大编码格式
        • SDS详解
          • 代码结构
          • 为什么要重新设计
        • 源码解析
          • 三大编码格式
      • hash数据类型
        • ziplist和hashtable编码格式
        • ziplist详解
          • 结构剖析
          • ziplist的优势(为什么要在设计一个结构出来)
          • zlentry详解
        • OBJ_ENCODING_HT详解
      • list数据类型
        • quicklist编码格式
        • 压缩配置项
          • list-compress-depth
          • list-max-ziplist-size
      • set数据类型
        • set-max-intset-entries
      • zset数据类型
        • skiplist(跳跃表)
          • 复杂度
          • 优缺点
    • 最终总结
      • 结构表
      • 结构图示
      • 时间复杂度
      • 文字描述

总纲

redis的k,v键值对

redis是key-value存储系统,其中key类型一般为字符串,value类型为redis对象(redisObject)
redis定义了redisObject结构体,来表示string,hash,list,set,zset等数据类型
每个键值对都会有一个dictEntry
dictEntry源码的构造

新的三大类型

  • bitmap: 本质是string
  • hyperLogLog: 本质是string
  • GEO: 本质是Zset

五种经典数据类型

redisObject

为了便于操作,redis采用了redisObject结构来统一五种不同的数据类型,这样所有的数据类型就都可以以相同的形式在函数间传递而不使用特定的类型结构.同时,为了识别不同的数据类型,redisObject中定义了type和encoding字段对不同的数据类型加以区分.简单的讲,redisObject就是stirng,hash,list,set,zset的父类,可以在函数间传递时隐藏具体的类型信息,所以作者把他抽象出来.

结构图示

redisObject结构

结构讲解

  • type:4 指的是当前值对象的数据类型
  • encoding:4 指的是当前值对象底层存储的编码类型
  • lru:LRU_BITS 采用lru算法清除内存中的对象
  • int refcount 记录对象引用次数
  • void *ptr 指向真正的底层数据结构的指针

数据类型与数据结构关系图示

图示

string数据类型

三大编码格式

格式解释补充说明
int保存long型(长整型)的64位(8个字节)有符号整数只有整数才会使用int,如果是浮点数,Redis内部会转为字符串值在保存,如果是整数,超过long的最大值的话就会转变为embstr类型
embstrSDS(Simple Dynamic String简单动态字符串),保存长度小于44字节的字符串长度大于44字节就会转变为raw类型
raw保存长度大于44字节的字符串

SDS详解

Redis没有直接复用C语言的字符串,而是新建了属于自己的结构----SDS.在Redis数据库里,包含字符串的键值对都是由SDS实现的(Redis中所有的键都是由字符串对象实现的,即底层是由SDS实现,Redis中所有的值对象中包含的字符串对象底层也是由SDS实现)

代码结构
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* 当前字符数组的长度 */
    uint8_t alloc; /* 当前字符数组总共分配的内存大小 */
    unsigned char flags; /* 当前字符串数组的属性,用来标识是sdshdr8还是sdshdr16等等 */
    char buf[];    /* 字符串真正的值 */
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

有多个定义,数值越高,存储的字符数组越大

为什么要重新设计
C语言SDS
字符串长度处理需要从头遍历,一直到遇到\0为止,时间复杂度为O(N)直接记录当前字符串长度,直接读取,时间复杂度为O(1)
内存重新分配分配内存空间超过后,会导致数组下标越级或者内存分配溢出空间预分配:sds修改以后len长度小于1M,那么将会分配与len长度相同的未使用空间,如果修改后大于1m,那么将分配1m的使用空间 惰性空间释放:sds缩短不会回收内存,而是用free记录多余的空间,后续变更直接使用free记录的空间,减少内存的分配次数
二进制安全二进制数据不是规则的字符串格式,可能会包含特殊字符,比如 \0等,此时C语言就会读取结束根据len长度来判断字符串结束的,二进制的安全问题就得以解决了

源码解析

Redis启动时会预先建立10000个分别是0-9999的redisObject变量作为共享对象,这就意味着如果set字符串的键值在0-9999之间的话,则可以直接指向共享对象而不需要在建立新对象,此时键值不占空间

三大编码格式

redis的Object.c源代码的tryObjectEncoding方法

/* Try to encode a string object in order to save space */
robj *tryObjectEncoding(robj *o) {
    long value;
    sds s = o->ptr;
    size_t len;

    serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);

    if (!sdsEncodedObject(o)) return o;

     if (o->refcount > 1) return o;

    len = sdslen(s);
    if (len <= 20 && string2l(s,len,&value)) {
      //字符串如果小于等于20且字符串转long型成功
        if ((server.maxmemory == 0 ||
            !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
            value >= 0 &&
            value < OBJ_SHARED_INTEGERS)
        {
           //配置maxmemory且值在10000以内.直接使用共享对象的值
            decrRefCount(o);
            incrRefCount(shared.integers[value]);
            return shared.integers[value];
        } else {
            if (o->encoding == OBJ_ENCODING_RAW) {
                sdsfree(o->ptr);
                o->encoding = OBJ_ENCODING_INT;
                o->ptr = (void*) value;
                return o;
            } else if (o->encoding == OBJ_ENCODING_EMBSTR) {
                decrRefCount(o);
                return createStringObjectFromLongLongForValue(value);
            }
        }
    }

    //如果长度小于embstr的长度,就转为embstr
    // 作者定义长度为44字节 #define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
    //embstr的含义就是嵌入式的string,从内存上讲就是字符串的sds结构体与其对应的redisObject对象
    //分配在同一块连续的内存空间
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
        robj *emb;

        if (o->encoding == OBJ_ENCODING_EMBSTR) return o;
        emb = createEmbeddedStringObject(s,sdslen(s));
        decrRefCount(o);
        return emb;
    }
    //大于44的直接就是raw对象
    trimStringObjectIfNeeded(o);

    /* 返回最终的对象 */
    return o;
}

对于embstr,由于其实现是只读的,因此在对embstr修改时,都会先转化为raw再进行修改,因此,只要是修改embstr对象,修改后的对> > 象一定是raw的,无论是否达到了44个字节
redis内部会根据用户给的不同键值而使用不同的编码格式,自适应的选择较优的内部编码格式,而这一切对用户完全透明

hash数据类型

ziplist和hashtable编码格式

参数名称参数解释默认值
hash-max-ziplist-entries使用压缩列表保存时哈希集合中的最大元素个数哈希对象保存的键值对数量小于512个
hash-max-ziplist-value使用压缩列表保存时哈希集合中单个元素的最大长度所有的键值对的键和值的字符串长度都小于等于64byte(一个英文字母一个字节)时用ziplist,反之用hashtable

hash类型键的字段数小于hash-max-ziplist-entries并且每个字段名和字段值的长度小于hash-max-ziplist-value的时候,redis才会使用OBJ_ENCODING_ZIPLIST(ziplist)来存储,前述的条件任意一个不满足就会转变为OBJ_ENCODING_HT(hashtable)的编码格式
ziplist升级到hashtable可以,但是他不会在进行降级

ziplist详解

ziplist是一种紧凑的编码格式,总体思想是多花时间来换取节约空间,即以部分读写性能为代价,来换取极高的内存空间利用率.因此只会用于字段个数少,且字段值也比较小的场景,压缩列表内存利用率极高的原因与其连续内存的热醒是分不开的

embstr也是嵌入式string,内存与redisObject是连续不分开的,redis在这方面特别重视(因为他主要就是玩内存的,他想去尽量的减少内存碎片)

结构剖析

ziplist是一个经过特殊编码的双向链表,不过他并不存储指向上一个节点和下一个节点的指针,而是去存储上一个节点长度和当前节点的长度.
结构图示:
结构图示意

属性类型长度用途
zlbytesuint32_t4字节记录整个压缩列表占用的内存字节数:在对压缩列表进行内存重分配,或者计算zlend的位置时使用
zltailuint32_t4字节记录压缩列表表尾节点距离压缩列表的起始地址有多少字节,通过这个偏移量,程序无须遍历整个压缩列表就可以确定表尾节点的地址
zllenuint16_t2字节记录了压缩列表包含的节点数量:当这个属性的值小于UINT16_MAX(65536)时,这个属性的值就是压缩列表包含节点的数量;当这个值等于UINT16_MAX时,节点的真实数量需要遍历整个压缩列表才能计算得出
entryX列表节点不定压缩列表包含的各个节点,节点的长度由节点保存的内容决定
zlenduint8_t1字节特殊值0xFF(二进制八个1),用于标记压缩列表的末端
ziplist的优势(为什么要在设计一个结构出来)
  • 普通的双向链表会有两个指针,在存储数据很小的情况下,我们存储的实际数据大小很可能还没有指针占用的内存大,得不偿失.所以ziplist没用指针,而是改为维护上一个和当前entry的长度,通过长度推算下一个元素在什么位置,牺牲读取的性能来获得最高效的存储空间,是典型的的时间换空间的思想.
  • 键表在内存中一般都是不连续的(因为可以靠指针,不用考虑连续的问题),遍历相对较慢,而ziplist正好解决了这个问题,他是内存上要求必须连续的,但是由于他每个节点的长度可以不相等,所以遍历的时候需要记录长度信息作为偏移量用来遍历整个链表.使之能够跳到上一个节点或者是下一个节点
  • 头节点里同时还存在一个参数len.和string类型的sds相似,这里是用来记录链表长度的,因此获取链表长度时不用再遍历整个链表,直接拿到len值就可以了,时间复杂度为O(1)
zlentry详解

entry的代码结构:

//压缩链表节点结构
typedef struct zlentry {
    unsigned int prevrawlensize; /* 存储上一个链表节点的长度数值所需要的字节数(1字节或者5字节,为了对齐填充) */
    unsigned int prevrawlen;     /*  上一个链表节点占用的长度 */
    unsigned int lensize;        /* 存储当前链表节点长度数值所需要的字节数*/
    unsigned int len;            /* 当前链表节点占用的长度 */
    unsigned int headersize;     /* 当前链表节点的头部大小(prevrawlensize+lensize),即非数据域大小 */
    unsigned char encoding;      /* 编码方式 */
    unsigned char *p;            /* 压缩链表以字符串的形式保存,该指针指向当前节点起始位置 */
} zlentry;

示例图解
存取示例图解

OBJ_ENCODING_HT详解

OBJ_ENCODING_HT这种编码方式内部才是真正的哈希表结构,可以实现O(1)级别的读写操作,在Redis内部,从OBJ_ENCODING_HT类型到底层真正的散列表数据结构是一层层的嵌套下去的,如图所示:

list数据类型

quicklist编码格式

在redis里面,list用quicklist来存储,quicklist存储了一个双向链表,每个节点都是一个ziplist,他是ziplist加上linkedlist的结合体
图示:
quicklist
代码示例图

压缩配置项

list-compress-depth

表示一个quicklist两端不被压缩的节点个数,这里的节点是quicklist双向链表的节点,含义如下

  • 0:是个特殊值,表示都不压缩,这是Redis的默认值
  • 1:表示quicklist两端各有一个节点不压缩,中间的节点压缩
  • 2:表示quicklist两端各有两个节点不压缩,中间的节点压缩
  • 3:表示quicklist两端各有三个节点不压缩,中间的节点压缩
    依次类推下去…
list-max-ziplist-size

当取正值的时候,表示按照数据项个数来限定每个quicklist节点上的ziplist长度.比如当这个数据项设置为5的时候,表示每个quicklist节点的ziplist最多包含五个数据项.当取负值的时候,表示按照占用字节数来限定每个quicklist节点上的ziplist长度.这时,他只能取-1到-5这五个值

  • -5: 每个quicklist节点上的ziplist大小不能超过64KB
  • -4: 每个quicklist节点上的ziplist大小不能超过32KB
  • -3: 每个quicklist节点上的ziplist大小不能超过16KB
  • -2: 每个quicklist节点上的ziplist大小不能超过8KB(-2也是Redis给的默认值)
  • -1: 每个quicklist节点上的ziplist大小不能超过4KB

set数据类型

Redis用inset和hashtable两种编码格式来存储set,如果元素都是整数类型并且个数小于512个,就用intset存储,如果不是整数类型,就用hashtable(数组加链表的存储结构).key就是元素的值,value为null

set-max-intset-entries

默认值为512,配置整型set的元素最大个数,超过的话就会变为hashtable

zset数据类型

当有序集合中包含的元素数量超过服务器属性server.zset_max_ziplist_entries的值(默认值是128),或者有序集合中新添加元素的member的长度大于服务器属性server.zset_max_ziplist_value的值(默认值为64)时,redis会使用跳跃表作为有序集合的底层实现。否则会使用ziplist作为有序列表的底层实现。

skiplist(跳跃表)

跳表是可以实现二分查找的有序链表.他是一种以空间换取时间的结构,由于链表无法进行二分查找,因此借鉴数据库的索引思想,提取出链表中关键节点(索引),先在关键节点上查找,在进入下层的链表查找,提取多层关键节点,就形成了二分查找的跳跃表结构.

总结来说跳表 = 链表 + 多级索引(两两取首升级为索引,一级一级进行嵌套)
结构示意图

复杂度

时间复杂度为O(logN),空间复杂度为O(n),新增修改删除的时间复杂度也是O(logN)

优缺点

典型的空间换时间的策略,而且只有在数据量大的时候才能体现出来优势,比较适应读多写少的场景,所以使用范围比较有限,这里需要注意,数据量大的同时写太多的话可能会一直需要调整各级别的索引,所以会耗时较多

最终总结

结构表

类型编码对象
REDIS_STRINGREDIS_ENCODING_INT使用整数值实现的字符串对象
REDIS_STRINGREDIS_ENCODING_EMBSTR使用embstr编码的简单动态字符串实现的嵌入式字符串对象
REDIS_STRINGREDIS_EMCODING_RAW使用简单动态字符串实现的字符串
REDIS_LISTREDIS_ENCODING_ZIPLIST使用压缩列表ziplist实现的列表对象
REDIS_LISTREDIS_ENCODING_LINKEDLIST使用双端链表quicklist实现的列表对象
REDIS_HASHREDIS_ENCODING_ZIPLIST使用压缩列表ziplist实现的哈希对象
REDIS_HASHREDIS_ENCODING_HT使用字典hashtable实现的哈希对象
REDIS_SETREDIS_ENCODING_INSET使用整数集合实现的集合对象
REDIS_SETREDIS_ENCODING_HT使用字典实现的集合对象
REDIS_ZSETREDIS_ENCODING_ZIPLIST使用压缩列表实现的有序集合对象
REDIS_ZSETREDIS_ENCODING_SKIPLIST使用跳跃表和字典实现的有序集合对象

结构图示

redis底层数据结构图示

时间复杂度

文字描述

  • string字符串(redis会根据当前值的类型和大小决定使用哪种内部编码实现)
  • int:8个字节的长整型
  • embstr:小于等于44个字节的嵌入式字符串(内存地址与redisObject连续)
  • raw:大于44个字节的字符串
  • hash哈希
  • ziplist(压缩列表):当哈希元素个数小于hash-max-ziplist-entries配置(默认512个),同时所有值都小于hash-max-ziplist-value配置(默认64字节)时,Redis会采用ziplist作为哈希的内部实现,ziplist内存更加的紧凑和节省,所以节约内存方面比起来hashtable要优秀
  • hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis就会改用hashtable作为哈希的内部实现,因为此时的ziplist的读写效率下降,而hashtable的读写复杂度为O(1)
  • list列表
  • ziplist:当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置(默认是64个字节)时.Redis会采用ziplist来作为列表的内部实现来减少内存的使用
  • linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会采用quicklist作为内部实现.quick是linkedlist和ziplist的结合,以ziplist为节点的链表(linkedlist)
  • set集合
  • inset(整数集合):当集合中的元素都是整数且元素个数小于set-max-inset-entries配置(默认512个)时,Redis会采用inset来作为集合的内部实现,从而减少内存的使用
  • hashtable:当集合类型无法满足inset时,Redis会采用hashtable作为内部实现
  • zset有序集合
  • ziplist:当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会采用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用
  • skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降

感觉这些多种编码数据结构实现的设计就是在时间和空间上寻求一种平衡,毕竟redis追求的是极致的性能,他就是从这些小细节的架构上体现出来的高性能的标杆

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TypeScript 基础学习之泛型和 extends 关键字

越来越多的团队开始使用 TS 写工程项目&#xff0c; TS 的优缺点也不在此赘述&#xff0c;相信大家都听的很多了。平时对 TS 说了解&#xff0c;仔细思考了解的也不深&#xff0c;借机重新看了 TS 文档&#xff0c;边学习边分享&#xff0c;提升对 TS 的认知的同时&#xff0c;…

Qt静态扫描(命令行操作)

Qt静态扫描&#xff08;命令行操作&#xff09; 前沿&#xff1a; 静态代码分析是指无需运行被测代码&#xff0c;通过词法分析、语法分析、控制流、数据流分析等技术对程序代码进行扫描&#xff0c;找出代码隐藏的错误和缺陷&#xff0c;如参数不匹配&#xff0c;有歧义的嵌…

Linux查看UTC时间

先了解一下几个时间概念。 GMT时间&#xff1a;Greenwich Mean Time&#xff0c;格林尼治平时&#xff0c;又称格林尼治平均时间或格林尼治标准时间。是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间。 GMT时间存在较大误差&#xff0c;因此不再被作为标准时间使用。现在…

数据传输服务DTS的应用场景(阿里巴巴)

数据传输服务DTS的应用场景(阿里巴巴) 数据传输服务DTS&#xff08;Data Transmission Service&#xff09;支持数据迁移、数据订阅和数据实时同步功能&#xff0c;帮助您实现多种典型应用场景。 不停机迁移数据库 传输方式&#xff1a;数据迁移 为了保证数据的一致性&#…

【17】组合逻辑 - VL17/VL19/VL20 用3-8译码器 或 4选1多路选择器 实现逻辑函数

VL17 用3-8译码器实现全减器 【本题我的也是绝境】 因为把握到了题目的本质要求【用3-8译码器】来实现全减器。 其实我对全减器也是不大清楚,但是仿照对全加器的理解,全减器就是低位不够减来自低位的借位 和 本单元位不够减向后面一位索要的借位。如此而已,也没有很难理解…

Python3简单实现图像风格迁移

导语T_T之前似乎发过类似的文章&#xff0c;那时候是用Keras实现的&#xff0c;现在用的PyTorch&#xff0c;而且那时候发的内容感觉有些水&#xff0c;于是我决定。。。好吧我确实只是为了写点PyTorch练手然后顺便过来水一篇美文~~~利用Python实现图像风格的迁移&#xff01;&…

Python实现性能测试(locust)

一、安装locustpip install locust -- 安装&#xff08;在pycharm里面安装或cmd命令行安装都可&#xff09;locust -V -- 查看版本&#xff0c;显示了就证明安装成功了或者直接在Pycharm中安装locust:搜索locust并点击安装&#xff0c;其他的第三方包也可以通过这种方式二、loc…

JavaScript Math(算数)对象

Math&#xff08;算数&#xff09;对象的作用是&#xff1a;执行常见的算数任务。在线实例round()如何使用 round()。random()如何使用 random() 来返回 0 到 1 之间的随机数。max()如何使用 max() 来返回两个给定的数中的较大的数。&#xff08;在 ECMASCript v3 之前&#xf…

站外seo优化有用吗?值得投入时间和精力吗?

随着互联网的普及和竞争的激烈化&#xff0c;SEO&#xff08;Search Engine Optimization&#xff0c;搜索引擎优化&#xff09;已经成为各种网站推广的必备技能。 而站外SEO优化就是指通过在其他网站上增加链接和引用等方式&#xff0c;来提高自己网站的搜索引擎排名和曝光度…

【6G 新技术】6G数据面介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

window.onresize的详细使用

最近做的项目老是涉及到大小屏切换&#xff0c;但是因为屏幕宽高不一样的原因&#xff0c;老是要计算表格高度 window.onresize&#xff1a;监听window窗口变化&#xff0c;当窗口大小发生变化时&#xff0c;会触发此事件 含义 MDN中的定义是这样子的&#xff1a; 文档视图调…

GitHub与PicGo搭建免费稳定图床并实现Typora内复制自动上传

本文介绍基于Github平台与PicGo工具&#xff0c;构建免费、稳定的图床&#xff0c;并实现在Typora内撰写Markdown文档时&#xff0c;粘贴图片就可以将这一图片自动上传到搭建好的图床中的方法。 1 配置GitHub 首先&#xff0c;我们需要配置Github&#xff0c;创建一个仓库从而…

mysql 查询一个表的数据,并修改部分数据,再插回原来的表中,复制某个用户的数据给另一个用户

mysql 查询一个表的数据&#xff0c;并修改部分数据&#xff0c;再插回原来的表中&#xff0c;复制某个用户的数据给另一个用户 一、需求 我有一表日记的表&#xff0c;表中盛放着所有用户的日记数据。 在做演示项目的时候&#xff0c;我需要将一个用户的数据复制给另一个用户…

PlotNeuralNet + ChatGPT创建专业的神经网络的可视化图形

PlotNeuralNet&#xff1a;可以创建任何神经网络的可视化图表&#xff0c;并且这个LaTeX包有Python接口&#xff0c;我们可以方便的调用。 但是他的最大问题是需要我们手动的编写网络的结构&#xff0c;这是一个很麻烦的事情&#xff0c;这时 ChatGPT 就出来了&#xff0c;它可…

JavaScript学习笔记(3.0)

数组是一种特殊类型的对象。在JavaScript中对数组使用typeof运算符会返回“object”。 但是&#xff0c;JavaScript数组最好以数组来描述。 数组使用数字来访问其“元素”。比如person[0]访问person数组中的第一个元素。 <!DOCTYPE html> <html> <body>&l…

【JavaEE进阶】——第一节.Maven国内源配置

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 操作步骤 1.打开项目配置界面&#xff08;当前项目配置&#xff09; 2.检查并配置国内源 3.再次打开项目配置界面&#xff08;新项目配置&#xff09; 4…

Android RecyclerView的notify方法和动画的刷新详解

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂&#xff0c;风趣幽默"&#xff0c;感觉非常有意思,忍不住分享一下给大家。 &#x1f449;点击跳转到教程 前言&#xff1a; 本篇讲解了RecyclerView关于通知列表刷新的常用的notify方法。和Recy…

综合练习7 摄氏度转华氏温度(“\t“的使用,循环语句)

综合练习7 摄氏度转华氏温度 使用do…while循环&#xff0c;在控制台输入摄氏温度与华氏温度的对照表。 对照表从摄氏温度-30℃到50℃&#xff0c;每行间隔10℃&#xff0c;运行如下&#xff1a; 摄氏温度&#xff1a;-30℃ 华氏温度&#xff1a;-22.0℉ 摄氏温度&#xff1a;…

【专项训练】动态规划-3

动态规划:状态转移方程、找重复性和最优子结构 分治 + 记忆化搜索,可以过度到动态规划(动态递推) function DP():# DP状态定义# 需要经验,需把现实问题定义为一个数组,一维、二维、三维……dp =[][] # 二维情况for i = 0...M:

自动化测试的定位及一些思考

大家对自动化的理解&#xff0c;首先是想到Web UI自动化&#xff0c;这就为什么我一说自动化&#xff0c;公司一般就会有很多人反对&#xff0c;因为自动化的成本实在太高了&#xff0c;其实自动化是分为三个层面的&#xff08;UI层自动化、接口自动化、单元测试&#xff09;&a…