Redis源码---内存友好的数据结构该如何细化设计
目录
前言
内存友好的数据结构
SDS 的内存友好设计
redisObject 结构体与位域定义方法
嵌入式字符串
压缩列表和整数集合的设计
节省内存的数据访问
-
前言
- Redis 是内存数据库,所以,高效使用内存对 Redis 的实现来说非常重要
- 而实际上,Redis 主要是通过两大方面的技术来提升内存使用效率的,分别是数据结构的优化设计与使用,以及内存数据按一定规则淘汰
- 关于内存数据按规则淘汰,这是通过 Redis 内存替换策略实现的,也就是将很少使用的数据从内存中淘汰,从而把有限的内存空间用于保存会被频繁访问的数据
- 这部分的设计与实现,主要和内存替换策略有关
- Redis 数据结构在面向内存使用效率方面的优化,其中包括两方面的设计思路:
- 一是内存友好的数据结构设计
- 二是内存友好的数据使用方式
- 这两方面的设计思路和实现方法是具有通用性的,当你在设计系统软件时,如果需要对内存使用精打细算,以便节省内存开销,这两种设计方法和实现考虑就非常值得学习和掌握
-
内存友好的数据结构
- 首先要知道,在 Redis 中,有三种数据结构针对内存使用效率做了设计优化,分别是
- 简单动态字符串(SDS)
- 压缩列表(ziplist)
- 整数集合(intset)
-
SDS 的内存友好设计
- 在第 2 讲中就已经介绍过 SDS 的结构设计,这里先做个简单的回顾:
- SDS 设计了不同类型的结构头,包括 sdshdr8、sdshdr16、sdshdr32 和 sdshdr64
- 这些不同类型的结构头可以适配不同大小的字符串,从而避免了内存浪费
- 不过,SDS 除了使用精巧设计的结构头外,在保存较小字符串时,其实还使用了嵌入式字符串的设计方法
- 这种方法避免了给字符串分配额外的空间,而是可以让字符串直接保存在Redis 的基本数据对象结构体中
- 所以这也就是说,要想理解嵌入式字符串的设计与实现,就需要先来了解下,Redis 使用的基本数据对象结构体 redisObject 是什么样的
-
redisObject 结构体与位域定义方法
- redisObject 结构体是在 server.h 文件中定义的,主要功能是用来保存键值对中的值
- 这个结构一共定义了 4 个元数据和一个指针:
- type:redisObject 的数据类型,是应用程序在 Redis 中保存的数据类型,包括 String、List、Hash 等
- encoding:redisObject 的编码类型,是 Redis 内部实现各种数据类型所用的数据结构
- lru:redisObject 的 LRU 时间
- refcount:redisObject 的引用计数
- ptr:指向值的指针
- 下面的代码展示了 redisObject 结构体的定义:
- 从代码中可以看到,在 type、encoding 和 lru 三个变量后面都有一个冒号,并紧跟着一个数值,表示该元数据占用的比特数
- 其中,type 和 encoding 分别占 4bits
- 而 lru 占用的比特数,是由 server.h 中的宏定义 LRU_BITS 决定的,它的默认值是 24bits
- 如下所示:
- 而这里要掌握的,就是这种变量后使用冒号和数值的定义方法
- 这实际上是 C 语言中的位域定义方法,可以用来有效地节省内存开销
- 这种方法比较适用的场景是,当一个变量占用不了一个数据类型的所有 bits 时,就可以使用位域定义方法,把一个数据类型中的 bits,划分成多个位域,每个位域占一定的 bit 数
- 这样一来,一个数据类型的所有 bits 就可以定义多个变量了,从而也就有效节省了内存开销
- 此外,你可能还会发现,对于 type、encoding 和 lru 三个变量来说,它们的数据类型都是unsigned
- 已知一个 unsigned 类型是 4 字节,但这三个变量,是分别占用了一个unsigned 类型 4 字节中的 4bits、4bits 和 24bits
- 因此,相较于三个变量,每个变量用一个 4 字节的 unsigned 类型定义来说,使用位域定义方法可以让三个变量只用 4 字节,最后就能节省 8 字节的开销
- 所以,当你在设计开发内存敏感型的软件时,就可以把这种位域定义方法使用起来
- 了解了 redisObject 结构体和它使用的位域定义方法以后,再来看嵌入式字符串是如何实现的
-
嵌入式字符串
- 前面说过,SDS 在保存比较小的字符串时,会使用嵌入式字符串的设计方法,将字符串直接保存在 redisObject 结构体中
- 然后在 redisObject 结构体中,存在一个指向值的指针ptr,而一般来说,这个 ptr 指针会指向值的数据结构
- 这里就以创建一个 String 类型的值为例,Redis 会调用 createStringObject 函数,来创建相应的 redisObject,而这个 redisObject 中的 ptr 指针,就会指向 SDS 数据结构,如下图所示:
- 在 Redis 源码中,createStringObject 函数会根据要创建的字符串的长度,决定具体调用哪个函数来完成创建
- 那么针对这个 createStringObject 函数来说,它的参数是字符串 ptr 和字符串长度 len
- 当len 的长度大于 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 这个宏定义时,createStringObject函数会调用 createRawStringObject 函数,否则就调用 createEmbeddedStringObject 函数
- 而在分析的 Redis 5.0.8 源码版本中,这个 OBJ_ENCODING_EMBSTR_SIZE_LIMIT默认定义为 44 字节
- 这部分代码如下所示:
- 现在就来分析一下 createStringObject 函数的源码实现,以此了解大于 44 字节的普通字符串和小于等于 44 字节的嵌入式字符串分别是如何创建的
- 首先,对于 createRawStringObject 函数来说,它在创建 String 类型的值的时候,会调用createObject 函数
- 补充:createObject 函数主要是用来创建 Redis 的数据对象的
- 因为 Redis 的数据对象有很多类型,比如 String、List、Hash 等
- 所以在 createObject 函数的两个参数中,有一个就是用来表示所要创建的数据对象类型,而另一个是指向数据对象的指针
- 然后,createRawStringObject 函数在调用 createObject 函数时,会传递OBJ_STRING 类型,表示要创建 String 类型的对象,以及传递指向 SDS 结构的指针,如以下代码所示
- 这里需要注意的是,指向 SDS 结构的指针是由 sdsnewlen 函数返回的,而 sdsnewlen 函数正是用来创建 SDS 结构的
- 最后,再来进一步看下 createObject 函数
- 这个函数会把参数中传入的、指向 SDS 结构体的指针直接赋值给 redisObject 中的 ptr,这部分的代码如下所示:
- 为理解普通字符串创建方法,看下图
- 这也就是说,在创建普通字符串时,Redis 需要分别给 redisObject 和 SDS 分别分配一次内存,这样就既带来了内存分配开销,同时也会导致内存碎片
- 因此,当字符串小于等于 44 字节时,Redis 就使用了嵌入式字符串的创建方法,以此减少内存分配和内存碎片
- 而这个创建方法,就是由前面提到的 createEmbeddedStringObject 函数来完成的
- 该函数会使用一块连续的内存空间,来同时保存 redisObject 和 SDS 结构
- 这样一来,内存分配只有一次,而且也避免了内存碎片
- createEmbeddedStringObject 函数的原型定义如下,它的参数就是从 createStringObject函数参数中获得的字符串指针 ptr,以及字符串长度 len
- 那么下面,就来具体看看,createEmbeddedStringObject 函数是如何把 redisObject和 SDS 放置在一起的
- 首先,createEmbeddedStringObject 函数会分配一块连续的内存空间,这块内存空间的大小等于 redisObject 结构体的大小、SDS 结构头 sdshdr8 的大小和字符串大小的总和
- 并且再加上 1 字节
- 注意,这里最后的 1 字节是 SDS 中加在字符串最后的结束字符“\0”
- 这块连续内存空间的分配情况如以下代码所示:
- 可以参考下图,其中展示了这块内存空间的布局
- 那么 createEmbeddedStringObject 函数在分配了内存空间之后,就会创建 SDS 结构的指针 sh
- 并把 sh 指向这块连续空间中 SDS 结构头所在的位置,下面的代码显示了这步操作
- 其中,o 是 redisObject 结构体的变量,o+1 表示将内存地址从变量 o 开始移动一段距离,而移动的距离等于 redisObject 这个结构体的大小
- 经过这步操作后,sh 指向的位置就如下图所示:
- 紧接着,createEmbeddedStringObject 函数会把 redisObject 中的指针 ptr,指向 SDS 结构中的字符数组
- 如以下代码所示,其中 sh 是刚才介绍的指向 SDS 结构的指针,属于 sdshdr8 类型
- 而sh+1 表示把内存地址从 sh 起始地址开始移动一定的大小,移动的距离等于 sdshdr8 结构体的大小
- 这步操作完成后,redisObject 结构体中的指针 ptr 的指向位置就如下图所示,它会指向SDS结构头的末尾,同时也是字符数组的起始位置:
- 最后,createEmbeddedStringObject 函数会把参数中传入的指针 ptr 指向的字符串,拷贝到 SDS 结构体中的字符数组,并在数组最后添加结束字符
- 这部分代码如下所示:
- 下面这张图,也展示了 createEmbeddedStringObject 创建嵌入式字符串的过程,可以再整体来看看:
- 总之,你可以记住,Redis 会通过设计实现一块连续的内存空间,把 redisObject 结构体和SDS 结构体紧凑地放置在一起
- 这样一来,对于不超过 44 字节的字符串来说,就可以避免内存碎片和两次内存分配的开销了
- 而除了嵌入式字符串之外,Redis 还设计了压缩列表和整数集合,这也是两种紧凑型的内存数据结构
-
压缩列表和整数集合的设计
- 首先要知道,List、Hash 和 Sorted Set 这三种数据类型,都可以使用压缩列表(ziplist)来保存数据
- 压缩列表的函数定义和实现代码分别在 ziplist.h 和 ziplist.c 中
- 不过,在 ziplist.h 文件中其实根本看不到压缩列表的结构体定义
- 这是因为压缩列表本身就是一块连续的内存空间,它通过使用不同的编码来保存数据
- 这里为了方便理解压缩列表的设计与实现,先来看看它的创建函数 ziplistNew,如下所示:
- 实际上,ziplistNew 函数的逻辑很简单,就是创建一块连续的内存空间,大小为ZIPLIST_HEADER_SIZE 和 ZIPLIST_END_SIZE 的总和,然后再把该连续空间的最后一个字节赋值为 ZIP_END,表示列表结束
- 另外要注意的是,在上面代码中定义的三个宏 ZIPLIST_HEADER_SIZE、ZIPLIST_END_SIZE 和 ZIP_END,在 ziplist.c 中也分别有定义,分别表示 ziplist 的列表头大小、列表尾大小和列表尾字节内容,如下所示:
- 那么,在创建一个新的 ziplist 后,该列表的内存布局就如下图所示
- 注意,此时列表中还没有实际的数据
- 然后,当往 ziplist 中插入数据时,ziplist 就会根据数据是字符串还是整数,以及它们的大小进行不同的编码
- 这种根据数据大小进行相应编码的设计思想,正是 Redis 为了节省内存而采用的
- 那么,ziplist 是如何进行编码呢?
- 要学习编码的实现,要先了解 ziplist 中列表项的结构
- ziplist 列表项包括三部分内容,分别是前一项的长度(prevlen)、当前项长度信息的编码结果(encoding),以及当前项的实际数据(data)
- 下面的图展示了列表项的结构(图中除列表项之外的内容分别是 ziplist 内存空间的起始和尾部)
- 实际上,所谓的编码技术,就是指用不同数量的字节来表示保存的信息
- 在 ziplist 中,编码技术主要应用在列表项中的 prevlen 和 encoding 这两个元数据上
- 而当前项的实际数据data,则正常用整数或是字符串来表示
- 所以这里就先来看下 prevlen 的编码设计
- ziplist 中会包含多个列表项,每个列表项都是紧挨着彼此存放的,如下图所示:
- 而为了方便查找,每个列表项中都会记录前一项的长度
- 因为每个列表项的长度不一样,所以如果使用相同的字节大小来记录 prevlen,就会造成内存空间浪费
- 举个例子,假设统一使用 4 字节记录prevlen,如果前一个列表项只是一个字符串“redis”,长度为 5 个字节,那么用 1 个字节(8 bits)就能表示 256 字节长度(2 的8 次方等于 256)的字符串了
- 此时,prevlen 用 4 字节记录,其中就有 3 字节是浪费掉了
- 再回过头来看,ziplist 在对 prevlen 编码时,会先调用 zipStorePrevEntryLength函数,用于判断前一个列表项是否小于 254 字节
- 如果是的话,那么 prevlen 就使用 1 字节表示
- 否则,zipStorePrevEntryLength 函数就调用 zipStorePrevEntryLengthLarge 函数进一步编码
- 这部分代码如下所示:
- 也就是说,zipStorePrevEntryLengthLarge 函数会先将 prevlen 的第 1 字节设置为 254
- 然后使用内存拷贝函数 memcpy,将前一个列表项的长度值拷贝至 prevlen 的第 2 至第 5 字节
- 最后,zipStorePrevEntryLengthLarge 函数返回 prevlen 的大小,为 5 字节
- 在了解了 prevlen 使用 1 字节和 5 字节两种编码方式后,再来学习下 encoding 的编码方法
- 一个列表项的实际数据,既可以是整数也可以是字符串
- 整数可以是 16、32、64等字节长度,同时字符串的长度也可以大小不一
- 所以,ziplist 在 zipStoreEntryEncoding 函数中,针对整数和字符串,就分别使用了不同字节长度的编码结果
- 下面的代码展示了 zipStoreEntryEncoding 函数的部分代码,可以看到当数据是不同长度字符串或是整数时,编码结果的长度 len 大小不同
- 简而言之,针对不同长度的数据,使用不同大小的元数据信息(prevlen 和 encoding),这种方法可以有效地节省内存开销
- 当然,除了 ziplist 之外,Redis 还设计了一个内存友好的数据结构,这就是整数集合(intset),它是作为底层结构来实现 Set 数据类型的
- 和 SDS 嵌入式字符串、ziplist 类似,整数集合也是一块连续的内存空间,这一点从整数集合的定义中就可以看到
- intset.h 和 intset.c 分别包括了整数集合的定义和实现
- 下面的代码展示了 intset 的结构定义
- 可以看到,整数集合结构体中记录数据的部分,就是一个 int8_t 类型的整数数组 contents
- 从内存使用的角度来看,整数数组就是一块连续内存空间,所以这样就避免了内存碎片,并提升了内存使用效率
- 到这里,就已经了解了 Redis 针对内存开销所做的数据结构优化,分别是 SDS 嵌入式字符串、压缩列表和整数集合
- 而除了对数据结构做优化,Redis 在数据访问上,也会尽量节省内存开销
-
节省内存的数据访问
- 在 Redis 实例运行时,有些数据是会被经常访问的,比如常见的整数,Redis 协议中常见的回复信息,包括操作成功(“OK”字符串)、操作失败(ERR),以及常见的报错信息
- 所以,为了避免在内存中反复创建这些经常被访问的数据,Redis 就采用了共享对象的设计思想
- 这个设计思想很简单,就是把这些常用数据创建为共享对象,当上层应用需要访问它们时,直接读取就行
- 现在就来做个假设
- 有 1000 个客户端,都要保存“3”这个整数
- 如果 Redis 为每个客户端,都创建了一个值为 3 的 redisObject,那么内存中就会有大量的冗余
- 而使用了共享对象方法后,Redis 在内存中只用保存一个 3 的 redisObject 就行,这样就有效节省了内存空间
- 以下代码展示的是 server.c 文件中,创建共享对象的函数 createSharedObjects,可以看下:
相关文章:
Redis源码---内存友好的数据结构该如何细化设计
目录 前言 内存友好的数据结构 SDS 的内存友好设计 redisObject 结构体与位域定义方法 嵌入式字符串 压缩列表和整数集合的设计 节省内存的数据访问 前言 Redis 是内存数据库,所以,高效使用内存对 Redis 的实现来说非常重要而实际上,R…...
获取 本周、本月、本年 的开始或结束时间
获取 本周、本月、本年 的开始或结束时间 public class DateTimeUtil{// 获取 本周、本月、本年 的开始或结束时间/// <summary>/// 获取开始时间/// </summary>/// <param name"TimeType">Week、Month、Year</param>/// <param name&quo…...
算法训练营 day58 动态规划 判断子序列 不同的子序列
算法训练营 day58 动态规划 判断子序列 不同的子序列 判断子序列 392. 判断子序列 - 力扣(LeetCode) 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而…...
优思学院|DFMEA是全球制造业的必修课!
DFMEA(Design Failure Mode and Effects Analysis)是一种分析技术,在产品设计的早期阶段识别和解决潜在的失效问题。它通过分析设计的各个方面,识别潜在的失效模式和影响,并提出相应的改进措施,以减少失效的…...
【Day02数据结构 空间复杂度】
最近太忙了都好久没有更新博客了,太难了,抽空水一篇文章,大佬们多多支持. 上篇:时间复杂度分析 目录 前言 一、空间复杂度概念? 二、实例展示 三、.有复杂度要求的算法题练习 1.题目链接:力扣--消失的数字 2.题目链接:力扣--旋转数组 总结: 1…...
多数据库管理工具哪家强?ChatGPT点评,第一位并不是Navicat
SQL逐渐成为职场必备的编程语言,相信大家都不陌生。SQL是一种结构化查询语言,是用于数据库之间通信的编程语言。每个数据库都有着自己独特的访问规则,但大体上是遵循SQL标准。 因此,辗转于不同的数据库之间,开发者或D…...
UnityShader常用函数(UnityShader内置函数、CG和GLSL内置函数等)
空间变换函数函数名描述float4 UnityWorldToClipPos(float3 pos )把世界坐标空间中某一点pos变换到齐次裁剪空间float4 UnityViewToClipPos(float3 pos )把观察坐标空间中某一点pos变换到齐次裁剪空间float3 UnityObjectToViewPos(float3 pos或float4 pos)模型局部空间坐标系中…...
Springboot自定义注解-1
注解用于修饰其他的注解(纪委:管干部的干部) ① Retention:定义注解的保留策略 Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含 Retention(RetentionPolicy.CLASS) …...
经纬度标定及大地坐标系相关概念(一)
经纬度标定及大地坐标系相关概念(一)一、背景二、经纬度的概念三、大地坐标系四、大地坐标系的分类五、各类坐标系介绍5.1 我国地理坐标系5.1.1 北京54坐标系5.1.2 1980西安坐标系5.1.3 2000国家大地坐标系5.2 世界大地坐标系5.1.1 WGS84坐标系5.3 加密坐…...
synchronized关键字原理
synchronized原理 1、基本特点 基于锁策略,可以知道synchronized具有以下特性: 1.开始时候是乐观锁,如果锁冲突频繁就转换为悲观锁 2.开始是轻量级锁,如果锁持有的时间较长,就转换成重量级锁 3.实现轻量级锁的时候…...
面试被问死怎么办?学会这四招,通过的机率提升30%
软件工程师面试很难,难到什么程度呢?有一句话可以来形容: 面试造飞机,上班拧螺丝 没错,面试的时候各种问你原理、机制,而这些在实际工作中却很难用到。于是乎,很多工程师面试的时候就非常害怕…...
Android TV UI开发常用知识
导入依赖 Google官方为Android TV的UI开发提供了一系列的规范组件,在leanback的依赖库中,这里介绍一些常用的组件,使用前需要导入leanback库。 implementation androidx.leanback:leanback:$version常用的页面 这些Fragment有设计好的样式&…...
Xshell 下载及安装
文章目录下载安装连接服务器Xshell 是一个强大的安全终端模拟软件,它支持SSH1, SSH2, 以及Microsoft Windows 平台的TELNET 协议。Xshell 通过互联网到远程主机的安全连接以及它创新性的设计和特色帮助用户在复杂的网络环境中享受他们的工作。 Xshell可以在Windows界…...
【LeetCode】剑指 Offer(12)
目录 题目:剑指 Offer 30. 包含min函数的栈 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 写在最后: 题目:剑指 Offer 30. 包含m…...
vue在history模式下打包部署问题解决
引言 项目使用的模板是element-template,由于业务需要,我将路由的hash模式更改为了history模式,然后在打包部署项目时就出现了问题 个人发现是资源的访问路径有问题,在部署之后发现每次访问的js资源路径前都会自动携带上我路由的…...
Java中常见性能优化策略的总结
文章目录1. 代码优化2. 数据库层面优化SQL调优架构层面的调优连接池调优3. 网络优化4. 缓存缓存分类使用场景选型考虑什么时候更新缓存?如何保障更新的可靠性和实时性?缓存是否会满,缓存满了怎么办?缓存是否允许丢失?丢…...
c++日志库log4cplus使用
项目中需要打印log,方便程序调试和问题定位分析。C实现的log4cplus日志库是一种易于使用的C 日志记录API,可提供线程安全,灵活且任意粒度的日志管理和配置控制。 下面介绍一下在linux中安装log4cplus库过程 下载地址:https://gi…...
什么是接口测试,我们如何实现接口测试?
1. 什么是接口测试 顾名思义,接口测试是对系统或组件之间的接口进行测试,主要是校验数据的交换,传递和控制管理过程,以及相互逻辑依赖关系。其中接口协议分为HTTP,WebService,Dubbo,Thrift,Socket等类型,测试类型又主…...
随机森林在sklearn中的实现
目录 一.集成算法 二.sklearn中的集成算法模块ensemble 三.RandomForestClassifier(随机森林分类器) 四.重要参数 1.基评估器参数 2.随机森林参数 五.重要属性和接口 六.Bagging的另一个必要条件 七.RandomForestRegressor(随机森林回归器) 八.机器学习中调参的基本思…...
[论文总结] 深度学习在农业领域应用论文笔记11
深度学习在农业上的应用笔记11 最近发表的相关论文数量不多,质量普遍也不尽如人意,尤其是《Computers and Electronics in Agriculture》这个期刊。这些论文的方法都很简单,只是强行将深度学习应用于某个问题上,而没有考虑到农业…...
Android 9.0 SystemUI 状态栏屏蔽弹出的悬浮式通知
1.概述 在9.0的系统ROM产品定制化开发中,在systemui的状态栏中,会在有闹钟 wifi连接等特殊弹窗通知的时候,会在接收到系统通知时,弹窗悬浮式弹窗通知,然后过几秒中, 就消失了,所以像这样的悬浮式通知,在有些产品中是不需要的,要求屏蔽掉,这就需要按照悬浮式流程来分析…...
商简智能计划与排程SPS在纺织行业中的应用
企业背景 某织造、染色及后整理一体化工艺的纺织面料企业,主要从事户外功能运动服装、内衣、泳衣、汽车内饰等面料的研发和销售,年产值在20亿左右,是迪卡侬运动面料最优质供应商之一。 纺织行业特点 印染具有典型的流程行业特性,…...
549、RocketMQ详细入门教程系列 -【消息队列之 RocketMQ(三)】 2023.02.28
目录一、Spring 整合 RocketMQ1.1 消息生产者1.2 消息消费者1.3 Spring 配置文件1.4 运行实例程序二、参考链接一、Spring 整合 RocketMQ 不同于 RabbitMQ、ActiveMQ、Kafka 等消息中间件,Spring 社区已经通过多种方式提供了对这些中间件产品集成,例如通…...
如何使用SpringBoot ⽇志?
Spring Boot自定义日志的打印:在一个类中先获取到打印日志对象(日志框架提供的日志对象,而日志框架默认已经集成到Spring Boot里了,springboot默认使用 slf4jlogback);注意:得到日志对象Logger ->来自于slf4j2、使用目志对象提…...
山东大学数字图像处理实验:MATLAB的图像显示方法
文章目录MATLAB 学习实验目的实验原理及方法实验内容MATLAB的图像显示方法实验目的实验内容MATLAB 学习 实验目的 了解 MATLAB 的基本功能及操作方法。掌握典型离散信号的 Matlab 产生和显示。 实验原理及方法 在 MATLAB 中, 序列是用矩阵向量表示, 但它没有包含采样信息, …...
Java缓存面试题——Redis解决方案
文章目录1、什么是缓存击穿?该如何解决2、什么是缓存穿透?该如何解决3、什么是缓存雪崩?该如何解决4、什么是BigKey?该如何解决bigkey的危害发现bigkey解决bigkey5、redis过期策略都有哪些?6、讲一讲Redis缓存的数据一…...
Flink:The generic type parameters of ‘Collector‘ are missing 类型擦除
类型擦除问题处理报错日志描述问题描述报错解决其他方法方法一:TypeInformation方法二:TypeHint报错日志描述 报错日志: The generic type parameters of Collector are missing. In many cases lambda methods dont provide enough informa…...
MySQL查询操作
系列文章目录前言一、简单查询SELECT子句SELECT后面之间跟列名DISTINCT,ALL列表达式列更名WHERE子句WHERE子句中可以使用的查询条件比较运算BETWEEN...AND...集合查询:IN模糊查询LIKE空值比较:IS NULL多重条件查询SELECT 的基本结构ORDER BY子句排序聚集…...
Redis-day01-note
Redis-day01-note 文章目录**Redis-day01-note****安装****配置文件详解****数据类型****字符串类型(string)**列表数据类型(List)****与python交互**Redis介绍特点及优点 1、开源的,使用C编写,基于内存且支持持久化 2、高性能的…...
嵌入式C基础知识(19)
时序在前面我们说到当处理器要向外设芯片写数据时,需要先将所需访问的外设的地址放在地址总线上,然后,由译码器将地址总线上的数据转换成片选信号,片选信号则使能目标外设芯片,接下来处理器写数据到数据总线上…...
做网站不备案用香港空间可以吗/网络营销渠道有哪三类
系列目录: 程序员之网络安全系列(一):为什么要关注网络安全?程序员之网络安全系列(二):如何安全保存用户密码及哈希算法程序员之网络安全系列(三):数据加密之…...
wordpress建手机版/武汉网站seo推广
产品负责人PO与团队的互动一直是一个难题。典型的问题在于:敏捷开发倡导“迭代期内无变更”以换取“团队承诺”,而实际上产品负责人却会不断地来提变更,打乱开发计划了。我们应该怎么办呢?产品负责人说“敏捷就是拥抱变化…...
阿里云 wordpress建站/太仓网站制作
前言无论是自己要在精心P过的自拍上添加个性文字,或者是摄影爱好者要在拍摄的作品里添加水印,亦或是在网页或者移动应用中实时生成文字和图片的组合,我们都需要找到一个合适且靠谱的方法来将图片和文字完美的结合在一起。所以,“最…...
西安年网站建设/重庆网站建设与制作
基于springboot和vue实现地方美食分享网站演示开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Maven包…...
网站做任务给钱的/国内打开google网页的方法
大家在登录网站的时候,大部分时候是通过一个表单提交登录信息。但是有时候浏览器会弹出一个登录验证的对话框,如下图,这就是使用HTTP基本认证。下面来看看一看这个认证的工作过程:第一步:客户端发送http request 给服务器,服务器验证该用户是…...
网站建设藤设计/淘宝运营培训班哪里有
题库来源:安全生产模拟考试一点通公众号小程序 2022年G2电站锅炉司炉上岗证题库系G2电站锅炉司炉复习题模拟预测卷!2022G2电站锅炉司炉考试模拟100题及答案依据G2电站锅炉司炉新考试大纲。G2电站锅炉司炉国家题库通过安全生产模拟考试一点通上提高应试能…...