对Redis 的数据结构的更深刻理解

news2024/11/26 23:38:59

文章目录

    • 简单动态字符串
      • SDS与C字符串的区别
    • 链表
    • 字典
      • 哈希算法 —— 添加新键值对的过程
      • rehash
        • rehash一般过程
      • 渐进式rehash
        • 渐进式rehash的详细步骤
    • 跳跃表
      • 实现
    • 整数集合intset
      • 升级
        • 步骤
        • 升级好处
      • 降级
    • 压缩列表 ziplist
      • ziplistnode
      • 连锁更新
    • 对象
      • 字符串对象
      • 列表对象
      • 哈希对象
        • 编码转换
      • 集合对象
        • 编码转换
      • 有序集合对象
      • 内存回收
      • 对象共享

下面是我在阅读《Redis设计与实现》书籍的一些记录,不少可能直接用了原文,因为原文写得实在是太好了,希望作者和读者见谅。

简单动态字符串

当Redis需要的不仅仅是一个字符串字面量,而是一个可以被修改的字符串值时,Redis 就会使用SDS来表示字符串值,比如在Redis的数据库里面,包含字符串值的键值对在底层都 是由SDS实现的。

除了用来保存数据库中的字符串值之外,SDS还被用作缓冲区( buffer):AOF模块中 的AOF缓冲区,以及客户端状态中的输入缓冲区,都是由SDS实现的

在这里插入图片描述

SDS与C字符串的区别

  1. 常数复杂度获取字符串长度,因为SDS在len属性中记录了SDS本身的长度,所以获取一个SDS长度的 复杂度仅为O(1)
  2. 杜绝缓冲区溢出, 当SDS API 需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,如果不满足的 话,API会自动将SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,所以使用SDS既不需要手动修改SDS的空间大小,也不会出现前面所说的缓冲区溢出问题。
  3. 减少修改字符串时带来的内存重分配次数:C语言缺陷:,在增长or缩短字符串时,通常要要先通过内存重分配来改变底层数组的空间大小,因为内存重分配涉及复杂的算法,并且可能需要执行系统调用,所以它通常是一个比较耗时的操作Redis做法:为了避免C字符串的这种缺陷,SDS通过未使用空间解除了字符串长度和底层数组长度之间的关联:在SDS中,buf数组的长度不一定就是字符数量加一,数组里面可以包含未使用的字节,而这些字节的数量就由SDS的free属性记录。
  4. 通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略: 通过空间预分配策略,Redis可以减少连续执行字符串增长操作所需的内存重分配次 数。惰性空间释放用于优化SDS的字符串缩短操作:当SDS的API需要缩短SDS保存的字符串 时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待将来使用。
  5. 二进制安全
  6. 兼容部分C字符串函数

链表

当一个列表键 包含了数量比较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis就会使 用链表作为列表键的底层实现。

除了链表键之外,发布与订阅、慢查询、监视器等功能也用到了链表,Redis服务器本 身还使用链表来保存多个客户端的状态信息,以及使用链表来构建客户端输出缓冲区 ( output buffer)。
在这里插入图片描述

字典

字典是一种用于保存键值对(key-value pair)的抽象数据结构。
字典经常作为一种数据结构内置在很多高级编程语言里面,但Redis所使用的C语言并没有内置这种数据结构,因此Redis构建了自己的字典实现。
字典在Redis中的应用相当广泛,比如Redis的数据库就是使用字典来作为底层实现的, 对数据库的增、删、查、改操作也是构建在对字典的操作之上的。

字典结构:
在这里插入图片描述

Redis的字典使用哈希表作为底层实现一个哈希表里面可以有多个哈希表节点,而每 个哈希表节点就保存了字典中的一个键值对。

哈希表结构:
在这里插入图片描述

注意:哈希表大小掩码 永远为 size - 1,原因如下:
sizemask:哈希表大小掩码,用于计算索引值
sizemask 的值一定为size - 1,即数组的最大索引,因为只有这样hash & sizemask 就保证得出的 index一定是一个小于等于 sizemask 的值,即一定在数组内
以 size 为 4 为例,sizemask 为 3
4 二进制为 0100,3 二进制为 0011
hash & 0011 保证能落到所有槽位 0~3

哈希算法 —— 添加新键值对的过程

当要将一个新的键值对添加到字典里面时

  1. 先根据键值对的键计算出哈希值,Redis 使用 MurmurHash2 算法来计算键的哈希值。
  2. 使用哈希值和sizemask计算索引值,
  3. 根据索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上 面。

如何解决哈希冲突
Redis的哈希表使用链地址法( separate chaining)来解决键冲突,每个哈希表节点都有一 个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的 多个节点可以用这个单向链表连接起来,这就解决了键冲突的问题。

rehash

随着操作的不断执行,哈希表保存的键值对会逐渐地增多或者减少,为了让哈希表的负载因子(load factor)维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩。扩展和收缩哈希表的工作可以通过执行rehash(重新散列)操作来完成,

rehash指的是重新计算键的哈希值 和索引值,然后将键值对放置到ht[1]哈希表的指定位置上。

rehash一般过程

  1. 为字典的ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(也即是ht[0].used属性的值)
  2. 将保存在ht[0]中的所有键值对rehash到ht[1]上面, 即重新计算键的哈希值 和索引值,然后将键值对放置到ht[1]哈希表的指定位置上。
  3. 当ht[0]包含的所有键值对都迁移到了ht[1]之后(ht[0]变为空表),释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备。

什么时候发生Rehash ?

以下条件中的任意一个被满足时,程序会自动开始对哈希表执行扩展操作:
1)服务器目前没有在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负 载因子大于等于1。
2)服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载 因子大于等于5。
负载因子=哈希表已保存节点数量/哈希表大小
load_factor = ht[0].used / ht[0].size

更深层次原因:
根据BGSAVE命令或BGREWRITEAOF命令是否正在执行,服务器执行扩展操作所需的 负载因子并不相同,这是因为在执行BGSAVE命令或BGREWRITEAOF命令的过程中,Redis 需要创建当前服务器进程的子进程,而大多数操作系统都采用写时复制( copy-on-write)技 术来优化子进程的使用效率,所以在子进程存在期间,服务器会提高执行扩展操作所需的负 载因子,从而尽可能地避免在子进程存在期间进行哈希表扩展操作,这可以避免不必要的内 存写入操作,最大限度地节约内存。

渐进式rehash

为了避免rehash对服务器性能造成影响,服务器不是一次性将ht[0]里面的所有键 值对全部rehash到ht[1],而是分多次、渐进式地将ht[0]里面的键值对慢慢地rehash到ht[1]

渐进式rehash的详细步骤

  1. 为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表
  2. 在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash工作 正式开始
  3. 在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成之后,程序将rehashidx属性的值增一`。
  4. 随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash至ht[1],这时程序将rehashidx属性的值设为-1,表示rehash操作已完成

本质上渐进式Re哈说是采取分而治之的方式,将rehash键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上,从而避免了集中式rehash而带来的庞大计算量。

跳跃表

跳跃表( skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的 指针,从而达到快速访问节点的目的。

Redis使用跳跃表作为有序集合键的底层实现之一

Redis只在两个地方用到了跳 跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构,除此之外,跳跃 表在Redis里面没有其他用途。

实现

Redis的跳跃表由redis.h/zskiplistNode和redis.h/zskiplist两个结构定义,

其中zskiplistNode 结构用于表示跳跃表节点

zskiplist结构则用于保存跳跃表节点的相关信息,比如节点的 数量,以及指向表头节点和表尾节点的指针等等
ZipList结构
在这里插入图片描述

在这里插入图片描述
level:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)。
length:记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内)
header:指向跳跃表的表头节点。
tail:指向跳跃表的表尾节点。
层(level):节点中用L1、L2、L3等字样标记节点的各个层,L1代表第一层,L2代表第二层,以此类推。每个层都带有两个属性:前进指针和跨度
后退(backward)指针:节点中用BW字样标记节点的后退指针,它指向位于当前节点的前一个节点。后退指针在程序从表尾向表头遍历时使用

整数集合intset

整数集合(intset)是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。

intset的结构
在这里插入图片描述

升级

每当我们要将一个新元素添加到整数集合里面,并且新元素的类型比整数集合现有所有 元素的类型都要长时,整数集合需要先进行升级( upgrade),然后才能将新元素添加到整 数集合里面。
其实,升级 有点类型转换的意思,从小的类型转成大的类型,类似于Java中int转long类型

步骤

  1. 根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间。
  2. 将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确的位上,而且在放置元素的过程中,需要继续维持底层数组的有序性质不变。
  3. 将新元素添加到底层数组里面。

升级好处

  1. 提升灵活性:因为整数集合可以通过自动升级底层数组来适应新元素,所以我们可以随意地将 int16_t、int32_t或者int64_t类型的整数添加到集合中,而不必担心出现类型错误,这种做法 非常灵活。
  2. 节约内存: 不用一下子就确定int_64的类型,而是在需要的时候再分配

降级

整数集合不支持降级操作,一旦对数组进行了升级,编码就会一直保持升级后的状态。

压缩列表 ziplist

压缩列表( ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表 项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现

压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构。一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。

ziplistnode

每个压缩列表节点都由previous_entry_length、encoding、content三个部分组成:

previous_entry_length属性以字节为单位,记录了压缩列表中前一个节点的长度。

因为节点的previous_entry_length属性记录了前一个节点的长度,所以程序可以通过指针运算,根据当前节点的起始地址来计算出前一个节点的起始地址。
举个例子,如果我们有一个指向当前节点起始地址的指针c,那么我们只要用指针c减去当前节点previous_entry_length属性的值,就可以得出一个指向前一个节点起始地址的指针p

压缩列表的从表尾向表头遍历操作就是使用这一原理实现的

encoding属性记录了节点的content属性所保存数据的类型以及长度

content属性负责保存节点的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定

连锁更新

现在,考虑这样一种情况:在一个压缩列表中,有多个连续的、长度介于250字节到253字节之间的节点e1至eN。

e1至eN的所有节点的previous_entry_length属性都 是1字节长的。

这时,如果我们将一个长度大于等于254字节的新节点new设置为压缩列表的表头节 点,那么new将成为e1的前置节点,因为e1的previous_entry_length属性仅长1字节,它没办法保存新节点new的长度,所以 程序将对压缩列表执行空间重分配操作,并将e1节点的previous_entry_length属性从原来的1字节长扩展为5字节长

正如扩展e1引发了对e2的扩展一样,扩展e2也会引发对e3的扩展,而扩展e3又会引发对 e4的扩展……为了让每个节点的previous_entry_length属性都符合压缩列表对节点的要求,程 序需要不断地对压缩列表执行空间重分配操作,直到eN为止。

Redis将这种在特殊情况下产生的连续多次空间扩展操作称之为连锁更新( cascade update)。

除了添加新节点可能会引发连锁更新之外,删除节点也可能会引发连锁更新。

因为连锁更新在最坏情况下需要对压缩列表执行N次空间重分配操作,而每次空间重分配的最坏复杂度为O(N),所以连锁更新的最坏复杂度为O(N^2)。

要注意的是,尽管连锁更新的复杂度较高,但它真正造成性能问题的几率是很低的。

对象

Redis并没有直接使用主要数据结构(比如简单动态字符串(SDS)、双端链表、字典、压缩列表、整数集合等等来)实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象,每种对象都用到了至少一种我们前面所介绍的数据结构。

使用对象的好处

  1. Redis可以在执行命令之前,根据对象的类型来判断一个对象是否可以执行给定的命令
  2. 们可以针对不同的使用场景,为对象设置多种不同的数据结构实现
  3. Redis的对象系统还实现了基于引用计数技术的内存回收机制,当程序不再 使用某个对象的时候,这个对象所占用的内存就会被自动释放;
  4. 另外,Redis还通过引用计 数技术实现了对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享同一 个对象来节约内存

Redis使用对象来表示数据库中的键和值,每次当我们在Redis的数据库中新创建一个键 值对时,我们至少会创建两个对象,一个对象用作键值对的键(键对象),另一个对象用作 键值对的值(值对象)。

在这里插入图片描述
通过encoding属性来设定对象所使用的编码,而不是为特定类型的对象关联一种固定的 编码,极大地提升了Redis的灵活性和效率,因为Redis可以根据不同的使用场景来为一个对 象设置不同的编码,从而优化对象在某一场景下的效率

字符串对象

如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于32字节,那么字符串对象将使用embstr编码的方式来保存这个字符串值

embstr编码是专门用于保存短字符串的一种优化编码方式,这种编码和raw编码一样, 都使用redisObject结构和sdshdr结构来表示字符串对象,但raw编码会调用两次内存分配函数 来分别创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配 一块连续的空间,空间中依次包含redisObject和sdshdr两个结构。

列表对象

列表对象的编码可以是ziplist或者linkedlist

ziplist编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点(entry)保存了 一个列表元素。

linkedlist编码的列表对象使用双端链表作为底层实现,每个双端链表节点 ( node)都保存了一个字符串对象,而每个字符串对象都保存了一个列表元素。

哈希对象

ziplist编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对 象时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了值的压缩 列表节点推入到压缩列表表尾。

编码转换

当哈希对象可以同时满足以下两个条件时,哈希对象使用ziplist编码`:

  1. 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;

  2. 哈希对象保存的键值对数量小于512个;

不能满足这两个条件的哈希对象需要使用 hashtable编码。

集合对象

集合对象的编码可以是intset或者hashtable。

intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存 在整数集合里面。

另一方面,hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部被设置为NULL。

编码转换

当集合对象可以同时满足以下两个条件时,对象使用intset编码

  1. 集合对象保存的所有元素都是整数值;
  2. 集合对象保存的元素数量不超过512个。

不能满足这两个条件的集合对象需要使用hashtable编码

有序集合对象

有序集合的编码可以是ziplist或者skiplist

ziplist编码的压缩列表对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在 一起的压缩列表节点来保存,第一个节点保存元素的成员(member),而第二个元素则保 存元素的分值( score)。

压缩列表内的集合元素按分值从小到大进行排序,分值较小的元素被放置在靠近表头的 方向,而分值较大的元素则被放置在靠近表尾的方向。

skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表。

zset结构中的zsl跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:跳跃表节点的object属性保存了元素的成员,而跳跃表节点的score属性则保存了元素的分值。通过这个跳跃表,程序可以对有序集合进行范围型操作,比如ZRANK、ZRANGE等命令就是基于跳跃表API来实现的。

除此之外,zset结构中的dict字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,而字典的值则保存了元素的分值。通过这个字典,程序可以用O(1)复杂度查找给定成员的分值ZSCORE命令就是根据这一特性实现的,而很多其他有序集合命令都在实现的内部用到了这一特性。

为什么有序集合需要同时使用跳跃表和字典来实现?

  1. 字典可以O(1)查找元素,跳跃表可以执行范围型操作,使用2种结构就可以兼得这2两个特性。
  2. 因为如果在一般情况下,查找操作为O(n),而字典结构的范围操作需要至少O(NlogN)时间复杂度,以及额外的O(N)内存空间

说白了,就是为了让有序集合的查找和范围型操作都尽可能快地执行,Redis选择了同时使用字典和跳跃表两种数据结构来实现有序集合

此外, 在实际中,字典和跳跃表会共享元素的成员和分值,所以并不会造成任何数据重复,也不会因此而 浪费任何内存。

内存回收

Redis在自己的对象系统中构建了一个引用 计数( reference counting)技术实现的内存回收机制,通过这一机制,程序可以通过跟踪对 象的引用计数信息,在适当的时候自动释放对象并进行内存回收。 每个对象的引用计数信息由redisObject结构的refcount属性记录。

对象的引用计数信息会随着对象的使用状态而不断变化:

  1. 在创建一个新对象时,引用计数的值会被初始化为1;
  2. 当对象被一个新程序使用时,它的引用计数值会被增一;
  3. 当对象不再被一个程序使用时,它的引用计数值会被减一;
  4. 当对象的引用计数值变为0时,对象所占用的内存会被释放

对象共享

除了用于实现引用计数内存回收机制之外,对象的引用计数属性还带有对象共享的作用

在Redis中,让多个键共享同一个值对象需要执行以下两个步骤:

  1. 将数据库键的值指针指向一个现有的值对象;
  2. 将被共享的值对象的引用计数增一。

例如:

目前来说,Redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从0到 9999的所有整数值,当服务器需要用到值为0到9999的字符串对象时,服务器就会使用这些 共享对象,而不是新创建对象。

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

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

相关文章

RK356X 解除UVC摄像头预览分辨率1080P限制

平台 RK3566 Android 11 概述 UVC: USB video class(又称为USB video device class or UVC)就是USB device class视频产品在不需要安装任何的驱动程序下即插即用,包括摄像头、数字摄影机、模拟视频转换器、电视卡及静态视频相机…

详解C++中的命名空间(namespace)

个人主页:平行线也会相交 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【C之路】 目录C关键字(C98)命名冲突命名空间命名空间的定义局部域和全局域的关系命名空间域小结命名空间中可以定义哪些内容嵌套命名空间…

音视频开发常用分析工具介绍

综述 工欲善其事,必先利其器;兵马未到,粮草先行。 在音视频开发过程中,利用工具可以更方便、更直观、更快捷的分析音视频的数据,便于开发过程中分析、调试和解决问题。 现总结一些音视频开发过程中常用的分析工具。…

Android library native 代码不能调试解决方法汇总

android native开发会碰到native代码无法调试问题,而app主工程中的native代码是可以调试的。如果项目中存在多个module,那么在application模块中依赖library模块,并且library模块中有native代码的时候,当debug library模块中的这些…

如何高效获取数据价值?

导读:上一篇《大数据架构知识点详解:国产数据库创新、湖仓一体实践…》中我们阐述了四大体系之数据架构体系,解释了云原生大数据实践、国产数据库创新变革、湖仓一体落地实践、OLAP 发展趋势四个论坛的架构思路。 接下来是四大体系之二数据效…

【Linux】2、Linux 的基本命令

目录一、Linux 的目录结构二、Linux 命令三、ls 命令四、cd五、pwd六、特殊路径符七、mkdir八、touch九、cat 命令十、more十一、cp十二、mv十三、rm十四、which十五、find十六、grep十七、wc十八、管道符十九、echo二十、重定向符二十一、tail 命令一、Linux 的目录结构 &…

直播观看指南|SOFA 五周年,Live Long and Prosper!

SOFA 五周年活动将于 2023 年 4 月 15 日(周六)12:00 在北京朝阳区恒通国际创新园 C6 栋 C work 举行!期待社区的小伙伴和对开源感兴趣的小伙伴们一起来现场玩哦~当然啦,不能来现场的小伙伴们也别担心,我们…

第二十一章 案例TodoList之新增数据

前一小节,我们已经完成了数据的动态展示,现在我们要完成数据的动态添加。如何添加呢?肯定是要通过Header组件来添加,但是Header组件如何将收集的任务数据,交给App组件并更新状态数据呢? 在Header组件中收集…

实现vue的条件渲染

我的需求是根据设备不同的状态 渲染不同的标签。设备状态用device_State表示。 在线上面是一个vue的标签,我有一个数据state ,如何让这个标签根据数据的取值 ,修改内容,如state1时,标签修改为离线 要根据数据的取值动态…

Python曲线拟合详解

文章目录入门参数多元拟合入门 scipy.optimize中,curve_fit函数可调用非线性最小二乘法进行函数拟合,例如,现在有一个高斯函数想要被拟合 yaexp⁡−(x−bc)2y a\exp-(\frac{x-b}{c})^2 yaexp−(cx−b​)2 则调用方法如下 import numpy as…

STM32基础代码学习G070CB串口透传调试(出厂默认)代码

先下载 一定记得回车换行勾选 可以参考“Quectel_BC260Y-CN_AT命令手册_V1.0.pdf” ATCGMI 查询制造商信息 ATCGMM 查询模块型号 ATCSQ 上报信号质量 ATCGATT? PS 域附着或去附着查看板子是否正常 再激活 ATQIACT1,最后查询ATQIACT? 配置阿里云mqtt atqmtc…

【从零开始学Skynet】实战篇《球球大作战》(十三):场景代码设计(下)

1、主循环 《球球大作战》是一款服务端运算的游戏,一般会使用主循环程序结构,让服务端处理战斗逻辑。如下图所示,图中的balls和foods代表服务端的状态,在循环中执行“食物生成”“位置更新”和“碰撞检 测”等功能,从而…

学习笔记 —— C++并行库OpenMP

ContentsInstallationImplement1、一个最简单的OpenMP代码:2、如何规定线程数2、如何设置OpenMP分配线程的schedule3、 冲突避免机制 --Reduction(规约)Discovery写在最前面: 并行化虽好,但并不是所有任务在并行化后都…

【MySQL学习】MySQL表的操作

目录一、表的创建1.1 创建表的语法1.2 案例二、查看表结构三、查看建表语句四、修改表4.1 修改表的语法4.2 修改案例五、删除表一、表的创建 1.1 创建表的语法 语法: CREATE TABLE table_name (field1 datatype,field2 datatype,field3 datatype ) character set…

透视Android系统AMS、PMS和WMS,了解开发中的重要角色

原理 在Android系统中,AMS(Activity Manager Service)、PMS(PackageManager Service)和WMS(Window Manager Service)是三个重要的系统服务,它们负责管理应用程序的生命周期、处理应…

Backblaze + Cloudflare + Picgo 打造免费顺畅的图床体验

本文xlog地址:https://x.cosine.ren/backblaze-cloudflare-picgo-imgbed hexo 地址:https://ysx.cosine.ren/backblaze-cloudflare-picgo-imgbed 同步发布 最近有用到oss存储的需求,跟群友调研了下国内 & 国外的 oss 后,深感找…

大爽pygame入门教程 第一节 基础知识

作者自我介绍:大爽歌, b站小UP主 ,编程1对1辅导老师 本节掌握要点: 文本, 图形, 键鼠事件,动态展示。 一、实现基础窗口 0 - 新建文件 新建001.py文件,内容如下 import pygamepygame.init() # pygame 初始化&#x…

什么是远程桌面连接?如何操作远程桌面?

随着时代越来越信息化、智能化、自动化,越来越多的企业和个人开始使用远程办公软件进行办公。其中远程桌面连接是一种非常流行的办公方式。那么,什么是远程桌面连接?如何操作远程桌面? 一、什么是远程桌面连接? 远程桌面连接是一种远程访问计算机的…

nginx缓冲区关闭导致下载失败问题-方案篇

问题描述 导出操作。小于200k的excel正常下载,大于200K的下载失败,异常信息如下: Cannot forward to error page for request [/xx/xx] as the response has already been committed. As a result,the response may have the wrong status code. If you…

程序员工作久了,都不会好好说人话了...互联网人的....黑话

原来工作也是会被腌入味的 前段时间有位博主吐槽 工作太久都不会说人话了 这张口的互联网味儿 瞬间梦回自己的工位 而评论区的网友们表示 这不就是”世另我“吗 一场关于互联网黑话的"掰头" 就此开始了... 维护厨房 (厨房秒变公司) 新…