【Redis】String源码剖析:512MB大字符串的内存管理之道

大家好,我是白晨,一个不是很能熬夜,但是也想日更的人。如果喜欢这篇文章,点个赞👍,关注一下👀白晨吧!你的支持就是我最大的动力!💪💪💪

在这里插入图片描述

文章目录

  • String源码剖析
    • redisObject
    • String底层解析
      • 概述
      • SDS
      • set是如何执行的?
  • 总结
      • Redis Object(redisObject)
      • String的底层实现
      • SDS的内部结构
      • SDS编码类型
      • String类型的最大长度
      • set命令的执行过程
      • 创建String对象的过程

String源码剖析


redisObject


在了解String的底层实现之前,我们首先要了解一下Redis中value是使用什么数据结构进行存储的。

如果对于面向对象思想理解比较深刻的同学应该有些思路,为了便于统一管理,value的结构应该都是继承于同一个基类,在基类上进行对于每个特定种类的value进行实现。

这种思路是非常正确的,虽然Redis是使用C语言进行开发的(C语言是面向过程的语言,没有官方支持的继承语法),但是其实现也是使用了面向对象的思想,在不同种类的value之上封装了一个redisObject的类,这就是所有value的基类。

img

查看redisObject的源码定义:

#define LRU_BITS 24
struct redisObject {unsigned type:4;       // 类型字段,占4位,用于标识对象的类型(如字符串、列表、集合等)unsigned encoding:4;   // 编码字段,占4位,用于存储对象采用的内部编码方式unsigned lru:LRU_BITS; /* * LRU位字段,位数由LRU_BITS定义,用于两种用途:* 1. 作为LRU时间,是相对于全局lru_clock的相对时间,用于跟踪对象的最近访问时间,以便进行LRU淘汰。* 2. 作为LFU数据,低8位是频率,高16位是访问时间,用于跟踪对象的使用频率和最近访问时间,以便进行LFU淘汰。*/int refcount;          // 引用计数,记录该对象被引用的次数void *ptr;             // 指针,指向实际存储对象数据的内存位置
};

先来看第一个类项type,它是用于标识对象的类型的,比如字符串、列表、集合等,也即我们日常操作Redis时使用的数据类型。

下面是常见数据结构type的取值:

/* The actual Redis Object */
#define OBJ_STRING 0    /* String object. */
#define OBJ_LIST 1      /* List object. */
#define OBJ_SET 2       /* Set object. */
#define OBJ_ZSET 3      /* Sorted set object. */
#define OBJ_HASH 4      /* Hash object. */

我们可以使用type命令来查看一个key对应的value是什么类型的。

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> type k1
string

接下来,来看第二个类项encoding,此字段用于存储对象采用的内部编码方式。上面type字段是告诉用户使用的是Redis支持的什么数据类型(比如String、List等),而encoding字段是告诉底层应该怎么实现这个数据类型,也即编码类型

encoding编码类型可能的取值及对应的编码类型的映射如下:

#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* No longer used: old hash encoding. */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* No longer used: old list/hash/zset encoding. */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of listpacks */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
#define OBJ_ENCODING_LISTPACK 11 /* Encoded as a listpack */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";}
}

最后,redisObject中最后一个类项ptr,指向实际存储对象数据的内存位置,也即指向encoding编码类型的具体实例。

img

String底层解析


概述

Redis中,String类型底层实现的数据结构为 intSDS(简单动态字符串)。

为什么不使用C原生的字符数组,而要使用SDS呢?

  1. 获取字符串长度

C字符串获取长度会使用strlen函数,时间复杂度为O(n),其获取字符串长度的逻辑如下图所示:
image-20240517104926288

SDS直接用一个类项记录了字符串长度,获取长度的时间复杂度为O(1)

  1. 字符串溢出

C字符串对于越界检查不是很完善,会出现内存踩踏的问题:

image-20240517105319107

SDS会记录可用空间,在需要扩容时自动扩容。

  1. 存储特殊类型数据

image-20240517105812069

如果要使用C字符串存储上面的字符串,要取字符串长度等函数全部都会出问题,因为C字符串约定以\0作为字符串的结尾标志。

但是使用SDS可用很好的存储这些特殊类型的数据:

image-20240517110144774

总结一下,C字符串和SDS的区别如下:

image-20240517104605891

可以发现,SDS在获取字符串长度、安全、内存分配等方面比C原生字符数组更加优越,所以使用SDS实现String类型。

SDS有两种encoding编码类型的实现,分别为embstrraw

所以,String类型底层实现的encoding编码类型有三种,分别为intembstr以及raw,其中embstrraw的只是相同数据结构——SDS的不同实现。

img

分别在什么情况下,使用上面不同的编码呢?

  • int:当存储的字符串全是整数值,并且这个整数值可以用long类型(8字节有符号整数)来表示时,此时使用int方式来存储。

image-20240517112007714

long类型数字最长为19位。

127.0.0.1:6379> set long 123456789# 可以转换为long类型,此时是int编码
OK
127.0.0.1:6379> object encoding long
"int"
127.0.0.1:6379> set long 12345678901234567890 # 20位整数,已经超出long类型的范围
OK
127.0.0.1:6379> object encoding long
"embstr"

image-20240517113037741

  • embstr:当存储的字符串长度小于44个字符时,此时使用embstr方式来存储。
  • raw:当存储的字符串长度大于44个字符时,此时使用raw方式来存储。
127.0.0.1:6379> set k1 xxxxxxxxxxxxxxxxxxxxxxxxx
OK
127.0.0.1:6379> strlen k1
(integer) 25
127.0.0.1:6379> object encoding k1
"embstr"
127.0.0.1:6379> set k2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OK
127.0.0.1:6379> strlen k2
(integer) 90
127.0.0.1:6379> object encoding k2
"raw"

image-20240517113445931

SDS

embstr编码方式会在一块连续的内存中创建RedisObject结构和SDS结构。这种方式在创建和删除时只需要分配和释放一次内存。此外,所有的数据都在一起,寻找起来更方便,并且更加遵循缓存局部性,增加缓存使用效率。但是,embstr存储的数据是只读的,如果需要修改embstr存储的值时,Redis底层会将String的存储方式由embstr转换为raw,然后再去修改其值。

127.0.0.1:6379> set k1 xxxxxxxxxxxxxxxxxx
OK
127.0.0.1:6379> strlen k1
(integer) 18
127.0.0.1:6379> object encoding k1
"embstr"
127.0.0.1:6379> append k1 yyyyy
(integer) 23
127.0.0.1:6379> strlen k1
(integer) 23
127.0.0.1:6379> object encoding k1
"raw"

raw编码方式会分别为RedisObject结构和SDS结构分配内存。这意味着在创建和删除时需要分配和释放两次内存,并且对于缓存的效率方面不如 embstr

image-20240517115036828

embstrraw的边界条件在Redis的不同版本中有所变化:

  • 在Redis 3.0、3.2以及4.0版本中,embstrraw的边界条件是39个字节。如果字符串长度小于等于39个字节,那么会使用embstr编码方式,否则会使用raw编码方式。
  • 在Redis 5.0和6.0版本中,embstrraw的边界条件变为44个字节。这是因为在这些版本中,Redis对内存的使用进行了优化,将原来的sdshdr结构改成了sdshdr16sdshdr32sdshdr64,其中的unsigned int变成了uint8_tuint16_t。这样的改变使得sdshdr的内存占用从原来的8个字节缩减为3个字节,因此embstrraw的边界条件从39个字节增加到了44个字节。

下面,我将结合Redis源码,详细解析String类型的底层实现,坐好了,开始发车。

首先,要了解String类型,肯定要了解SDS,首先查看Redis7中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[];
};
  • sdshdr5:这个结构体并未实际使用,只是用来说明类型为5的SDS字符串的布局。其中,flags字段的3个最低有效位用于存储类型,5个最高有效位用于存储字符串长度。
  • sdshdr8:这个结构体用于存储长度小于等于255的字符串。其中,len字段表示已使用的字符数,alloc字段表示分配的字符数(不包括头部和空字符),flags字段的3个最低有效位用于存储类型,5个最高有效位未使用。
  • sdshdr16:这个结构体用于存储长度小于等于65535的字符串。其中,lenalloc字段的类型变为uint16_t,可以存储更大的数值。
  • sdshdr32:这个结构体用于存储长度小于等于4294967295的字符串。其中,lenalloc字段的类型变为uint32_t,可以存储更大的数值。
  • sdshdr64:这个结构体用于存储长度小于等于18446744073709551615的字符串。其中,lenalloc字段的类型变为uint64_t,可以存储更大的数值。

可以看到Redis针对不同大小的字符串分别规划了不同的数据类型,比如sdshdr8len字段最大值为255,所以其存储的字符串的最大长度也不会超过255。所以,字符串长度不超过44字节的embstr的编码方式就使用的sdshdr8这个结构体。

那为什么限制字符串长度不超过44字节才能使用embstr的编码方式呢?

首先redisObject这个结构体的大小为16字节,sdshdr8不计算最后一个buf字段得到的结构体大小为 1 + 1 + 1 = 3 1+1+1=3 1+1+1=3 字节,由于embstr内存分配是连续,也即redisObjectSDS在同一连续空间,所以将其大小加和,此时需要连续内存 16 + 3 = 19 16+3=19 16+3=19 字节,假设此时buf中有效字符串为20字节,再加上1字节的\0,就一共需要 19 + 20 + 1 = 40 19+20+1=40 19+20+1=40 字节,而Linux分配内存最好都是按照2的次方数分配,否则会出现许多内存碎片,所以最后得到分配的内存为64( 2 6 2^6 26​)字节。

image-20240517113445931

现在我们只计算必要的内存开销,也即redisObject + sdshdr8中len、alloc以及flags + sdshdr8中buf的结束符\0,得到必要内存开销为 16 + 1 + 1 + 1 + 1 = 20 16+1+1+1+1=20 16+1+1+1+1=20 字节, 总共分配内存为64字节, 64 − 20 = 44 64-20=44 6420=44​ 字节 ,所以就得到了44字节。

所以当buf中字符数大于12时,小于44时,就会分配64字节,而一般字符串长度都是大于12字节的,所以限制字符串长度不超过44字节才能使用embstr的编码方式是为了更好的使用使用内存,提高内存的使用率,提高CPU访问速度

在Redis 3.0、3.2以及4.0版本中,embstrraw的边界条件是39个字节,这是因为当时SDS还没有按照字符串长度对其使用的结构体进行分类,具体结构如下面代码块中所示,当时的sdshdrsdshdr8大了5个字节,所以边界就为 44 − 5 = 39 44 - 5 = 39 445=39字节。

struct sdshdr {unsigned int len;// 4个字节unsigned int free;// 4个字节char buf[];
};

一张图总结SDS

image-20240517155611182

set是如何执行的?

现在有这样一条最简单的命令:set k1 v1,你知道它是怎么执行的吗?

先来看一下Redis内部是如何创建一个redisObject的:

typedef struct redisObject robj;
robj *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;
}

可以看到默认的编码格式就是raw类型。

直接看set命令对应的源码:

/* SET key value [NX] [XX] [KEEPTTL] [GET] [EX <seconds>] [PX <milliseconds>]*     [EXAT <seconds-timestamp>][PXAT <milliseconds-timestamp>] */
void setCommand(client *c) {robj *expire = NULL;int unit = UNIT_SECONDS;int flags = OBJ_NO_FLAGS;if (parseExtendedStringArgumentsOrReply(c,&flags,&unit,&expire,COMMAND_SET) != C_OK) {return;}c->argv[2] = tryObjectEncoding(c->argv[2]); // 此时value为raw类型编码,尝试对键的值进行重编码,以节省存储空间。setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL); // 执行SET命令,设置键的值并处理过期时间等参数
}

再来查看一下tryObjectEncoding这个函数:

robj *tryObjectEncoding(robj *o) {return tryObjectEncodingEx(o, 1);
}#define OBJ_SHARED_INTEGERS 10000
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
/* 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;/* Make sure this is a string object, the only type we encode* in this function. Other types use encoded memory efficient* representations but are handled by the commands implementing* the type. */serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);/* We try some specialized encoding only for objects that are* RAW or EMBSTR encoded, in other words objects that are still* in represented by an actually array of chars. */if (!sdsEncodedObject(o)) return o;/* It's not safe to encode shared objects: shared objects can be shared* everywhere in the "object space" of Redis and may end in places where* they are not handled. We handle them only as values in the keyspace. */if (o->refcount > 1) return o;/* Check if we can represent this string as a long integer.* Note that we are sure that a string larger than 20 chars is not* representable as a 32 nor 64 bit integer. */len = sdslen(s);if (len <= 20 && string2l(s,len,&value)) {/* 该对象可编码为long类型。尝试使用共享对象。* 注意,当使用maxmemory时,我们避免使用共享整数* 因为每个对象都需要有一个私有的LRU字段,这样LRU算法才能正常工作。*/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);}}}/* 如果字符串很小并且仍然是RAW编码,* 尝试更有效的EMBSTR编码。* 在这种表示中,对象和SDS字符串被分配* 在同一块内存中,以节省空间和缓存丢失。*/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):这个函数是一个包装函数,它调用tryObjectEncodingEx(o, 1)函数并返回结果。
  • robj *tryObjectEncodingEx(robj *o, int try_trim):这个函数尝试对字符串对象o进行编码。参数try_trim表示是否尝试优化SDS字符串。
    • 如果字符串长度小于等于20,并且可以转换为长整数,那么就会尝试将其编码为整数。
      • 如果服务器没有设置最大内存限制,或者服务器的最大内存策略允许使用共享整数,并且值在0到OBJ_SHARED_INTEGERS(10000)之间,那么会尝试使用共享整数对象来表示这个值。这是因为Redis预先创建了一些常用的整数对象,可以直接使用,这样可以节省内存。
      • 如果不能使用共享整数对象,那么会根据原来的编码方式来处理。如果原来的编码方式是OBJ_ENCODING_RAW,那么会释放原来的SDS字符串,将编码方式改为OBJ_ENCODING_INT,并将值存储在ptr字段中。如果原来的编码方式是OBJ_ENCODING_EMBSTR,那么会创建一个新的整数对象来存储这个值。
    • 如果o的长度小于等于OBJ_ENCODING_EMBSTR_SIZE_LIMIT(44),并且o仍然是RAW编码的,函数会尝试使用EMBSTR编码,这种编码方式更加高效。
    • 最后,如果函数无法对o进行编码,它会尝试优化SDS字符串,然后返回原始对象o

再来看看以embstrraw编码的对象的创建过程:

#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);
}// 创建以embstr编码的对象
robj *createEmbeddedStringObject(const char *ptr, size_t len) {robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);struct sdshdr8 *sh = (void*)(o+1);o->type = OBJ_STRING;o->encoding = OBJ_ENCODING_EMBSTR;o->ptr = sh+1;o->refcount = 1;o->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;
}// 创建以raw编码的对象
robj *createRawStringObject(const char *ptr, size_t len) {return createObject(OBJ_STRING, sdsnewlen(ptr,len));
}robj *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;
}

最后,为什么String类型最大长度为512MB

static int checkStringLength(client *c, long long size, long long append) {if (mustObeyClient(c))return C_OK;/* 'uint64_t' cast is there just to prevent undefined behavior on overflow */long long total = (uint64_t)size + append;/* Test configured max-bulk-len represending a limit of the biggest string object,* and also test for overflow. */if (total > server.proto_max_bulk_len || total < size || total < append) {addReplyError(c,"string exceeds maximum allowed size (proto-max-bulk-len)");return C_ERR;}return C_OK;
}

可以看到,String类型的最大长度由server.proto_max_bulk_len这个变量决定,那么这个变量的值为多少呢?

查看redis.conf文件可得:

# In the Redis protocol, bulk requests, that are, elements representing single     
# strings, are normally limited to 512 mb. However you can change this limit     
# here, but must be 1mb or greater     
#                                                                                
proto-max-bulk-len 512mb

可以得到,proto-max-bulk-len为默认为512mb,所以String类型最大长度为512MB

当然,也可以在Redis客户端查询单个字符串的最大长度:

127.0.0.1:6379> config get proto-max-bulk-len
1) "proto-max-bulk-len"
2) "536870912"

也可以得到512MB


总结


Redis Object(redisObject)

  • Redis使用redisObject作为所有value的基类,采用面向对象的设计思想。
  • redisObject包含类型(type)、编码(encoding)、LRU位字段、引用计数(refcount)和指向实际数据的指针(ptr)。

String的底层实现

  • Redis中String类型可以通过两种方式实现:整数(int)和简单动态字符串(SDS)。
  • SDS相比传统C字符串提供了获取长度的常数时间复杂度、更好的内存安全性和存储特殊数据的能力。

SDS的内部结构

  • SDS使用不同的头部结构(sdshdr5, sdshdr8, sdshdr16, sdshdr32, sdshdr64)来存储不同长度的字符串,以优化内存使用。
  • sdshdr5不用于实际存储,仅作为文档说明。
  • sdshdr8sdshdr16sdshdr32sdshdr64根据字符串长度使用不同大小的整数来存储长度和分配大小。

SDS编码类型

  • embstr:当SDS较短时使用,它将redisObject和SDS结构分配在同一块内存中,节省内存并提高缓存效率。
  • raw:当SDS较长时使用,redisObject和SDS结构分别分配内存,灵活性更高但内存分配效率较低。

String类型的最大长度

  • Redis中单个String类型的最大长度为512MB,由配置参数proto-max-bulk-len决定。

set命令的执行过程

  • set命令首先尝试对值进行编码优化,以节省空间。
  • 如果值可以表示为整数,且在共享整数范围内,Redis会使用共享整数对象。
  • 如果字符串较短,Redis会使用embstr编码。
  • 对于较长的字符串,Redis会使用raw编码。

创建String对象的过程

  • Redis提供了创建String对象的函数,根据字符串长度和编码类型创建相应的redisObject

如果讲解有不对之处还请指正,我会尽快修改,多谢大家的包容。

如果大家喜欢这个系列,还请大家多多支持啦😋!

如果这篇文章有帮到你,还请给我一个大拇指 👍和小星星 ⭐️支持一下白晨吧!喜欢白晨【Redis】系列的话,不如关注👀白晨,以便看到最新更新哟!!!

我是不太能熬夜的白晨,我们下篇文章见。

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

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

相关文章

13.js对象

定义 一种复杂数据类型&#xff0c;是无序的&#xff08;不保留键的插入顺序&#xff09;&#xff0c;以键值对&#xff08;{key:value})形式存放的数据集合 对象的创建 &#xff08;1&#xff09;字面量创建 var 对象名{ } &#xff08;2&#xff09;内部构造函数创建 v…

【C语言】C语言-学生成绩管理系统(源码+数据文件+课程论文)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

【iOS】——工厂设计模式

文章目录 一、设计模式创建型模式结构型模式行为型模式 二、设计模式七大准则三、简单工厂模式四、工厂方法模式五、抽象工厂模式 一、设计模式 设计模式是指在特定上下文中解决常见问题时所采用的一套可复用的解决方案。这些模式是面向对象编程中的通用概念&#xff0c;广泛应…

Docker安装OnlyOffice

工作需要&#xff0c;多人在线编辑同一文档&#xff0c;找了一圈发现onlyoffice满足需求&#xff0c;于是使用docker安装了社区版本。下面记录下安装过程。 Onlyoffice 是什么&#xff1f; Onlyoffice 是一个多端协同的 Office 办公套件&#xff0c;相当于微软的 Office365 全…

【Linux网络编程】传输层中的TCP和UDP(TCP篇)

【Linux网络编程】传输层中的TCP和UDP&#xff08;TCP篇&#xff09; 目录 【Linux网络编程】传输层中的TCP和UDP&#xff08;TCP篇&#xff09;TCP协议TCP协议段格式确认应答&#xff08;ACK&#xff09;机制&#xff08;保证可靠性&#xff09;超时重传机制连接管理机制理解T…

ingress-nginx控制器安装(ingress ImagePullBackOff )

支持的版本&#xff08;查看自己的kubernetes版本替换安装过程中的版本选择合适的版本安装&#xff09; 安装过程&#xff1a; 这里不采用helm的方式&#xff0c;而是采用YAML manifest的方式来安装。 下载ingress-nginx的https://raw.githubusercontent.com/kubernetes/ingr…

多线程事务

一、业务场景 我们在工作中经常会到往数据库里插入大量数据的工作&#xff0c;但是既需要保证数据的一致性&#xff0c;又要保证程序执行的效率。因此需要在多线程中使用事务&#xff0c;这样既可以保证数据的一致性&#xff0c;又能保证程序的执行效率。但是spring自带的Trans…

并发编程笔记7--并发编程基础

1、线程简介 1.1、什么是线程 现代操作系统中运行一个程序&#xff0c;会为他创建一个进程。而每一个进程中又可以创建许多个线程。现代操作系统中线程是最小的调度单元。 两者关系&#xff1a;一个线程只属于一个进程&#xff0c;而一个进程可以拥有多个线程。线程是一个轻量…

CS 下载安装详解

目录 CS简介&#xff1a; CS下载地址&#xff1a; CS的安装&#xff1a; CS简介&#xff1a; CS为目前渗透中常用的一款工具&#xff0c;它的强大在于控制windows木马&#xff0c;CS主要控制windows木马。 CS下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/…

WordPress Country State City Dropdown CF7插件 SQL注入漏洞复现(CVE-2024-3495)

0x01 产品简介 Country State City Dropdown CF7插件是一个功能强大、易于使用的WordPress插件,它为用户在联系表单中提供国家、州/省和城市的三级下拉菜单功能,帮助用户更准确地填写地区信息。同时,插件的团队和支持也非常出色,为用户提供高质量的服务。 0x02 漏洞概述 …

【Pytorch】【MacOS】14.m1芯片使用mps进行深度模型训练

读者要先自行安装python以及anaconda&#xff0c;并且配置pytorch环境 第一步 测试环境 import torch # 判断macOS的版本是否支持 print(torch.backends.mps.is_available()) # 判断mps是否可用 print(torch.backends.mps.is_built())如果第一个语句为False&#xff0c;说明当前…

Python简介

Python简介 1. Python定义 Python 是一种简单易学并且结合了解释性、编译性、互动性和面向对象的脚本语言。Python提供了高级数据结构&#xff0c;它的语法和动态类型以及解释性使它成为广大开发者的首选编程语言。 Python 是解释型语言&#xff1a; 开发过程中没有了编译这个环…

AIGC-常见图像质量评估MSE、PSNR、SSIM、LPIPS、FID、CSFD,余弦相似度----理论+代码

持续更新和补充中…多多交流&#xff01; 参考: 图像评价指标PNSR和SSIM 函数 structural_similarity 图片相似度计算方法总结 MSE和PSNR MSE: M S E 1 m n ∑ i 0 m − 1 ∑ j 0 n − 1 [ I ( i , j ) − K ( i , j ) ] 2 MSE\frac{1}{mn}\sum_{i0}^{m-1}\sum_{j0}^{n-1}[…

汽车展厅应用客流统计,洞察客户规律,完成热门车型分析

在汽车展厅中&#xff0c;客流统计正逐渐成为一项不可或缺的重要工具&#xff0c;它帮助我们洞察客户规律&#xff0c;从而能够更好地完成热门车型分析。 一、客流统计-客户画像分析 客流统计下的客户画像构建为我们提供了深入了解客户的途径。通过对进入展厅的人群进行细致分析…

2007NOIP普及组真题 4. Hanoi双塔问题

线上OJ&#xff1a; 【07NOIP普及组】Hanoi双塔问题 题解分析 1、本题考的其实不是Hanoi塔&#xff0c;而是瞪眼法&#xff08;数学推导&#xff09;和高精度。 2、本题不需要输出移动的顺序&#xff0c;只是输出移动的次数即可。 核心思想&#xff1a; 1、从上述图中&#x…

常见算法(3)

1.Arrays 它是一个工具类&#xff0c;主要掌握的其中一个方法是srot&#xff08;数组&#xff0c;排序规则&#xff09;。 o1-o2是升序排列&#xff0c;o2-o1是降序排列。 package test02; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparat…

PostgreSQL用户与角色简述

简述 PostgreSQL通过角色&#xff08;role&#xff09;来控制数据库的访问权限。角色可以拥有数据库对象&#xff08;比如表、函数等&#xff09;&#xff0c;并允许将这些对象的权限授予其他角色&#xff0c;从而实现对象访问的控制。角色&#xff08;role&#xff09;包含了…

虹科Pico汽车示波器 | 免拆诊断案例 | 2012 款雪佛兰科鲁兹车偶尔多个故障灯异常点亮

故障现象 一辆2012款雪佛兰科鲁兹车&#xff0c;搭载1.8 L 发动机&#xff0c;累计行驶里程约为9.6万km。该车组合仪表上的发动机故障灯、ABS故障灯及动力转向故障灯偶尔异常点亮&#xff0c;同时发动机转速表和发动机冷却液温度表的指针会突然归零&#xff0c;严重时发动机无…

独享IP是原生IP吗?二者有何区别?

原生IP&#xff1a; 原生IP是指由Internet服务提供商&#xff08;ISP&#xff09;直接分配给用户的IP地址&#xff0c;这些IP地址通常反映了用户的实际地理位置和网络连接。原生IP是用户在其所在地区或国家使用的真实IP地址&#xff0c;与用户的物理位置直接相关。在跨境电商中…

牛客NC367 第K个n的排列【困难 dfs,全排列问题 Java/Go/PHP/C++】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/1595969179464e4c940a90b36abb3c54 思路 全排列问题本文提供的答案在力扣同一道题60. 排列序列&#xff0c;超时了但是截止文章发表日&#xff0c;牛客上是能通过全部测试用例的Java代码 import java.util.*;pu…