Redis底层数据结构之String
文章目录
- 1. 前提回顾
- 2. RedisObject三大数据类型简介
- 3. SDS字符串
- 4. SDS字符串源码分析
- 5. 总结
1. 前提回顾
前面我们说到redis的String数据结构在底层有多种编码方式。例如我们执行下面两条语句
set k1 v1
set age 17
我们查看类型,发现这类型都是String类型

我们现在查看底层的编码类型,发现编码类型一个是embstr一个是int类型,虽然都是String字符串,但redis根据value的不同类型设置了不同的编码方式

redisObject内部对应3大物理编码:int、embstr和raw

2. RedisObject三大数据类型简介
- int
它保存long型(长整型)的64位(8个字节)有符号整数(9223372036854775807,最高19位)。

只有整数才会使用int类型去编码,如果是浮点数,redis内部其实先将浮点数转化为字符串值,然后再保存。

- emstr
代表embstr格式的SDS(Simple Dynamic String 简单动态字符串),保存长度小于44字节的字符串,embstr即embedded String,表示嵌入式的String
- raw
保存长度大于44字节的字符串
下面给演示一下各个类型的使用:
首先长度小于19位的数字,类型为int

浮点数底层为emstr

长度超过19的数字,类型为emstr

普通字符串是emstr

长度超过44的字符串,为raw类型

长度超过44的浮点数,为raw类型

3. SDS字符串
我们知道c语言底层表示字符串本质上是用的一个char数组。但是redis没有直接复用C语言的字符串的底层实现,而是新建了属于自己的结构—SDS,在redis数据库里,包含字符串值的键值对都是由SDS实现的(redis中所有的键都是由字符串对象实现的即底层是由SDS实现,Redis所有的值对象中包含的字符串对象底层也是SDS实现的)

我们看一下sds结构体的源码:
typedef char *sds;/* Note: sdshdr5 is never used, we just access the flags byte directly.* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {unsigned char flags; /* 3 lsb of type, and 5 msb of string length */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len; /* used */uint8_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits */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[];
};
可以看到redis底层工定义了五种sds字符串的结构,分别是:sdshdr5、sdshdr8、sdshdr16、sdshdr32和sdshdr64。那么各个字段是什么意思:
- len:代表存储的字符串的长度
- alloc:存储了为字符串分配的内存空间的长度(即实际分配的空间大小,不包括结构体头部和字符串结束符 \0)
- flags:代表SDS字符串的类型,上面介绍类型的五种之一
- buf[]:真正存储字符串的char数组
那上面五种类型有什么不同?
sdshdr5:表示 2 5 2^5 25即共32个字节
sdshdr8:表示 2 8 2^8 28即256个字节
sdshdr16:表示 2 16 2^{16} 216即65536个字节,64KB
sdshdr32:表示 2 32 2^{32} 232即4GB
sdshdr64:表示 2 64 2^{64} 264即17179869184G个字节
len表示SDS实际的长度,使我们在获取字符串的时候能够在O(1)时间复杂度情况下拿到,而不需要遍历一遍char数组
alloc:可以用来计算free就是字符串已经分配的未使用空间,这个值就可以引入预分配空间算法了,而不用去考虑内存分配问题
buf:字符串数组,真正存数据的
alloc 字段的作用是提供了对字符串进行动态增长的能力。当字符串需要扩容时,Redis 可以根据需要重新分配更大的内存空间,并将原有的内容复制到新的内存中,然后释放原来的内存。这样做的好处是可以减少频繁地进行内存分配和释放操作,提高了性能。举个例子,假设有一个 SDS 字符串,其实际长度为 10,但为了防止频繁的内存分配和释放操作,alloc 被设置为了 20。当这个字符串需要扩容时,Redis 可以直接在内存中分配一个长度为 20 的新空间,然后将原字符串内容复制到新的空间中,同时更新 len 和 alloc 字段。这样即使字符串长度增长,也不需要每次都重新分配内存,减少了系统的开销。
现在思考一个问题:Redis为什么不直接使用char数组,而是自己重写设计来一个SDS数据结构?
这是因为C语言没有Java里面的String类型,只能是靠自己的char[]来实现,字符串在C语言中的存储方式,如果现在我们想通过strlen命令获取value的长度,我们就需要从头开始遍历,知道遇到\0即可,这样操作的时间复杂度是O(n),所以redis没有直接使用C语言传统的字符串标识,而是自己构建了一种名为简单动态字符串类型SDS,并作为redis的默认字符串类型。

4. SDS字符串源码分析
当我们输入set k1 v1 底层到底发生了什么?这里我们详细分析一下
首先看t_string.c文件
/* SET key value [NX] [XX] [KEEPTTL] [GET] [EX <seconds>] [PX <milliseconds>]* [EXAT <seconds-timestamp>][PXAT <milliseconds-timestamp>] */
void setCommand(client *c) {//key的过期时间robj *expire = NULL;//时间的单位是sint unit = UNIT_SECONDS;int flags = OBJ_NO_FLAGS;if (parseExtendedStringArgumentsOrReply(c,&flags,&unit,&expire,COMMAND_SET) != C_OK) {return;}//c->args[2]就是value,这里调用tryObjectEncoding就是尝试堆value值进行编码,编码后的结果再保存到c->args[2]中c->argv[2] = tryObjectEncoding(c->argv[2]);setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
}
当我们在cli写set k1 v1时,它底层本质上调用的是上面的
setCommand命令
从上面代码代码可以看出字符串的编码和tryObjectEncoding这个函数时紧密相关的,我们来分析一下底层是怎么做的
- int
当字符串键值的内存可以用一个64位有符号整数表示时,Redis会将键值转化为long型来存储,此时即对于OBJ_ENDOCDING_INT编码类型。内存内存结构如下:

OBJECT_ENCODING_INT这个时Redis定义的一个常量,我们可以在object.c文件中看到
char *strEncoding(int encoding) {switch(encoding) {case OBJ_ENCODING_RAW: return "raw";case OBJ_ENCODING_INT: return "int";case OBJ_ENCODING_HT: return "hashtable";case OBJ_ENCODING_QUICKLIST: return "quicklist";case OBJ_ENCODING_LISTPACK: return "listpack";case OBJ_ENCODING_INTSET: return "intset";case OBJ_ENCODING_SKIPLIST: return "skiplist";case OBJ_ENCODING_EMBSTR: return "embstr";case OBJ_ENCODING_STREAM: return "stream";default: return "unknown";}
}
Redis启动时会预先建立10000个分别存储0~9999的redisObject变量做为共享变量,这意味着如果set字符串的键值在0~10000之间的话,就可以直接指向功效变量,而不需要再建立新对象,此时键值不占内存。(这和java中有些包装类设置缓存的原理一样)。如下面两条命令:
set k1 100
set k2 100

上面的缓存可以再server.h中可以看到定义:
#define OBJ_SHARED_INTEGERS 10000 //缓存去大小struct sharedObjectsStruct {robj *ok, *err, *emptybulk, *czero, *cone, *pong, *space,*queued, *null[4], *nullarray[4], *emptymap[4], *emptyset[4],*emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,*outofrangeerr, *noscripterr, *loadingerr,*slowevalerr, *slowscripterr, *slowmoduleerr, *bgsaveerr,*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *unlink,*rpop, *lpop, *lpush, *rpoplpush, *lmove, *blmove, *zpopmin, *zpopmax,*emptyscan, *multi, *exec, *left, *right, *hset, *srem, *xgroup, *xclaim, *script, *replconf, *eval, *persist, *set, *pexpireat, *pexpire, *time, *pxat, *absttl, *retrycount, *force, *justid, *entriesread,*lastid, *ping, *setid, *keepttl, *load, *createconsumer,*getack, *special_asterick, *special_equals, *default_username, *redacted,*ssubscribebulk,*sunsubscribebulk, *smessagebulk,*select[PROTO_SHARED_SELECT_CMDS],*integers[OBJ_SHARED_INTEGERS],*mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */*bulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "$<value>\r\n" */*maphdr[OBJ_SHARED_BULKHDR_LEN], /* "%<value>\r\n" */*sethdr[OBJ_SHARED_BULKHDR_LEN]; /* "~<value>\r\n" */sds minstring, maxstring;
};
下面我们看看redis时如何利用这些缓存起来的对象的
/* Try to encode a string object in order to save space */
robj *tryObjectEncodingEx(robj *o, int try_trim) {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)) {if ((server.maxmemory == 0 ||!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&value >= 0 &&value < OBJ_SHARED_INTEGERS){decrRefCount(o);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);}}}/* If the string is small and is still RAW encoded,* try the EMBSTR encoding which is more efficient.* In this representation the object and the SDS string are allocated* in the same chunk of memory to save space and cache misses. */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;}/* We can't encode the object...* Do the last try, and at least optimize the SDS string inside */if (try_trim)trimStringObjectIfNeeded(o, 0);/* Return the original object. */return o;
}robj *tryObjectEncoding(robj *o) {return tryObjectEncodingEx(o, 1);
}
关键是下面这几句代码
if ((server.maxmemory == 0 ||!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&value >= 0 &&value < OBJ_SHARED_INTEGERS){decrRefCount(o);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);}}
value < OBJ_SHARED_INTEGERS判断当前值是否小于10000,如何小于shared.integers[value]中直接拿值,上面也是对INT类型编码的核心源码
- embstr
上面我们分析了int类型是如何编码的,下面我们看看embstr类型是如何编码的
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)return createEmbeddedStringObject(ptr,len);elsereturn createRawStringObject(ptr,len);
}robj *createEmbeddedStringObject(const char *ptr, size_t len) {//创建redisObject对象并分配内存,使用 zmalloc 函数分配了一块内存,用于存储字符串对象。这块内存的大小包括了字符串对象结构体 robj 的大小、sdshdr8 结构体的大小以及字符串的长度 len 加上一个字节用于字符串的结束符 \0。robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);//定义了一个 sdshdr8 结构体指针 sh,它指向了 robj 结构体之后的内存地址。这是因为 robj 结构体之后的内存空间被用来存储字符串数据的头信息。struct sdshdr8 *sh = (void*)(o+1);//类型表示为String类型o->type = OBJ_STRING;//编码为embstr编码o->encoding = OBJ_ENCODING_EMBSTR;//设置了 robj 结构体中的 ptr 字段为指向 sdshdr8 结构体之后的内存地址,即指向字符串的实际内容。o->ptr = sh+1;//引用计数为1o->refcount = 1;//使用次数0o->lru = 0;sh->len = len;sh->alloc = len;sh->flags = SDS_TYPE_8;if (ptr == SDS_NOINIT)sh->buf[len] = '\0';else if (ptr) {memcpy(sh->buf,ptr,len);sh->buf[len] = '\0';} else {memset(sh->buf,0,len+1);}return o;
}
首先len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT判断当前字符串的长度是否小于44,这前面也讲过为什么是44,如果小于则符合emstr类型,则调用createEmbeddedStringObject方法。我们看到ptr指针指向的位置为sh+1。我们从源码可以看出sh是robj 结构体位置,这就说明一个现象,即字符串SDS结构体和其对应的redisObject对象分配在同一连续内存空间,就像讲sds嵌入到redisObject中。
简单来说redisObject和对应的sdshdr8是在内存汇总紧挨着的,使用 embstr 编码时,字符串的内容直接存储在 robj 结构体之后的内存空间中,而不需要额外分配存储空间。这样做的好处是减少了内存分配和内存碎片,提高了内存使用效率。
- raw
我们继续回到tryEncodingEx方法,如果我们对象的字节数大于44,我们此时就是创建raw类型
obj *createObject(int type, void *ptr) {robj *o = zmalloc(sizeof(*o));o->type = type;o->encoding = OBJ_ENCODING_RAW;o->ptr = ptr;o->refcount = 1;o->lru = 0;return o;
}
当字符串长度大于44的超长字符串时,Redis则将键值的内部编码方式改为OBJ_ENCODING_RAW格式,这与OBJ_ENCODING_EMBSTR的编码方式不同,此时动态字符串sds的内存与其依赖的redisObject内存不再连续。

下面看一种特殊情况:

5. 总结


redis底层会根据用户给的键值使用不同的编码格式,自适应地选择较优化的内存编码格式,而这一切对用户完全透明!
相关文章:
Redis底层数据结构之String
文章目录 1. 前提回顾2. RedisObject三大数据类型简介3. SDS字符串4. SDS字符串源码分析5. 总结 1. 前提回顾 前面我们说到redis的String数据结构在底层有多种编码方式。例如我们执行下面两条语句 set k1 v1 set age 17我们查看类型,发现这类型都是String类型 我们…...
【Maven学习笔记】Maven入门教程(适合新手反复观看学习)
Maven学习笔记 Maven的简要介绍Maven的安装和配置Maven的安装Maven安装的常用配置 Maven的使用入门编写pom编写主代码编写测试代码打包和运行使用Archetype生成项目骨架 Maven核心概念的阐述坐标案例分析依赖依赖的范围传递性依赖依赖范围依赖调节可选依赖Maven依赖常用的技巧 …...
idea Springboot 在线考试管理系统开发mysql数据库web结构java编程计算机网页
一、源码特点 springboot 在线考试管理系统是一套完善的完整信息系统,结合mvc框架和bootstrap完成本系统springboot spring mybatis ,对理解JSP java编程开发语言有帮助系统采用springboot框架(MVC模式开发),系统具有…...
Spring Cloud Alibab 入门搭建,包含Nacos中心,注册服务发现服务,Feign请求,GateWay网关,sentinel限流
源码在最后 一、安装Nacos注册中心 1.1查看Nacos官网,安装Nacos服务,下载源码或者安装包 1.2启动服务,默认端口为8848, 二、创建服务注册&发现 2.1使用脚手架,创建注册服务和发现服务项目,我用的版…...
ShardingSphere-SQL 解析 Issue 处理流程
ShardingSphere-SQL 解析 Issue 处理流程 这是之前给社区写的 SQL 解析 Issue 的处理流程,可以帮助社区用户快速参与到 ShardingSphere-SQL 解析任务当中。 ShardingSphere SQL 解析 issue 列表 Issue 背景说明 当前 Issue 使用自定义的爬虫脚本从对应的数据库官…...
【矩阵】48. 旋转图像【中等】
旋转图像 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 示例 1: 输入:matrix [[1,2,3],[4,5,6],[7,8,…...
高质量 Git 仓库汇总(持续更新,方便查看)
Leetcode https://github.com/kamyu104/LeetCode-Solutions Cmake https://github.com/viva64/pvs-studio-cmake-examples 3D目标检测 Awesome-3D-Object-Detection Awesome-3D-Object-Detection-for-Autonomous-Driving Cuda Code dbscan https://github.com/l3lackc…...
学习笔记-华为IPD转型2020:1,IPD的重要意义
华为产品开发转型:IPD计划 大多数公司发现,当公司大幅增长时,在较小规模上有效的管理实践不再有效。产品开发过程也是如此。随着华为的发展,该公司遇到了产品故障率更高、开发周期更长和研发成本增加等问题。然后,它转…...
【阿里云系列】-基于云效构建部署NodeJS项目到ACK
准备工作 01、编写Dockerfile文件可以根据不同的环境,新建不同的Dockerfile文件,比如Dockerfile-PROD # Deliver the dist folder with NginxFROM nginx:stable-alpine ENV LANGC.UTF-8 ENV TZAsia/ShanghaiCOPY dist/ /usr/share/nginx/html COPY ngi…...
Jmeter入参问题小记
表单入参的时候,这个地方需要勾选,如果不☑️选的话,会提示errorMsg":"Required String parameter code is not present",...
【四 (2)数据可视化之 Matplotlib 常用图表及代码实现 】
目录 文章导航一、介绍二、安装Matplotlib三、导入Matplotlib四、设置可以中文显示四、常用图形1、散点图(Scatter Plot)2.1、线性图(Line Plot)2.2、堆叠折线图2.3、多图例折线图3.1、柱状图/条形图(Bar Chart&#x…...
官网建设的江湖四大流派,一派苦撑、一派完犊子、另外两派搅局。
有人的地方就有江湖,有江湖的地方就有纷争,有纷争地方就有此起彼伏。 说好的,当赚够了钱就退出建站江湖,人就是江湖,怎么退? 官网建设风起云涌20年,一方倒地,一方揭竿而起ÿ…...
Ubuntu 安装 KVM 虚拟化
1. Ubuntu 安装 KVM 虚拟化 KVM 是 Linux 内核中一个基于 hypervisor 的虚拟化模块,它允许用户在 Linux 操作系统上创建和管理虚拟机。 如果机器的CPU不支持硬件虚拟化扩展,是无法使用KVM(基于内核的虚拟机)直接创建和运行虚拟机的。此时最多只能使用…...
SA3D:基于 NeRF 的三维场景分割方法
Paper: Cen J, Zhou Z, Fang J, et al. Segment anything in 3d with nerfs[J]. Advances in Neural Information Processing Systems, 2024, 36. Introduction: https://jumpat.github.io/SA3D/ Code: https://github.com/Jumpat/SegmentAnythingin3D SA3D 是一种用于 NeRF 表…...
mysql实战开发之 mysql 删除一张表某个字段的sql语句
有一张表, 我需要删除这张表其中的某一个或者某几个字段, 相信大家在日常开发中应该会遇到这种情况, 然后刚好自己接触的项目安装的mysql关闭了允许远程连接的设置, 也就是说不允许使用类似于navicat 等可视化工具连接, 那么就没办法通过可视化工具直接去通过鼠标操作就可以 完…...
Selenium控制已运行的Edge和Chrome浏览器——在线控制 | 人机交互(详细启动步骤和bug记录)
文章目录 前期准备1. 浏览器开启远程控制指令(1)Edge(2)Chrome 2. 执行python代码(1)先启动浏览器后执行代码(2)通过代码启动浏览器(3)Bug问题记录1ÿ…...
【Miniconda】Linux系统中 .condarc 配置文件的位置一般在哪里
【Miniconda】Linux系统中 .condarc 配置文件的位置一般在哪里 🌈 个人主页:高斯小哥 🔥 高质量专栏:Matplotlib之旅:零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程👈 希望得到…...
数据结构——动态顺序表
数据结构的动态顺序表有以下几个操作:创建,销毁,初始化,增删查改和打印以及内存空间不够时的扩容 本文的宏定义: #define SeqTypeData int 1.动态顺序表的创建 typedef struct SeqListInit{//动态顺序表的创建SeqT…...
Android Studio实现内容丰富的安卓宠物医院管理系统
获取源码请点击文章末尾QQ名片联系,源码不免费,尊重创作,尊重劳动 项目编号128 1.开发环境android stuido jdk1.8 eclipse mysql tomcat 2.功能介绍 安卓端: 1.注册登录 2.系统公告 3.宠物社区(可发布宠物帖子…...
华为OD机试真题-启动多任务排序-2024年OD统一考试(C卷)
题目描述: 一个应用启动时,会有多个初始化任务需要执行,并且任务之间有依赖关系,例如A任务依赖B任务,那么必须在B任务执行完成之后,才能开始执行A任务。 现在给出多条任务依赖关系的规则,请输入任务的顺序执行序列,规则采用贪婪策略,即一个任务如果没有依赖的任务,则…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
HTML前端开发:JavaScript 获取元素方法详解
作为前端开发者,高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法,分为两大系列: 一、getElementBy... 系列 传统方法,直接通过 DOM 接口访问,返回动态集合(元素变化会实时更新)。…...
