源码|redis7.2.2|sds

文章目录

  • 前言
  • Type && Encoding
  • sds
  • encoding
    • createStringObject
      • createEmbeddedStringObject
        • 总结
      • createRawStringObject
        • 总结
    • createStringObjectFromLongDouble
      • 总结
    • createStringObjectFromLongLongWithOptions
      • 总结
  • 相关操作
    • sdscatlen
      • 总结
  • 阈值44
  • sds VS C字符串

前言

从本篇文章开始会持续更新有关"redis数据结构源码"的分析。[分析的源码是redis7.2.2版本的,有时候会结合之前的版本]。由于能力有限,有些地方可能有些错误,还望指正。

Type && Encoding

redis的每种数据结构都被封装在"redisObject"中,先来看一下"redisObject"长什么样子。

struct redisObject {unsigned type:4;//表示数据结构的类型,占4bitsunsigned encoding:4;//数据结构底层使用的编码,占4bitsunsigned lru:LRU_BITS; //用于LRU或者LFU内存淘汰算法,占24bitsint refcount;//引用计数,占4bytes,引用计数变为0对象被销毁,内存被回收void *ptr;//指向具体的数据,占8bytes
};

接下来看一下"type"都有哪些[以下是五种基本的数据结构]

#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. */

接下来看一下数据结构对应的"encoding"
在这里插入图片描述
redis在3.2版本时引入了"quicklist"[也就是linkedlist+ziplist]
在这里插入图片描述
在这里插入图片描述
redis在5.0版本引入"listpack"
在这里插入图片描述
在这里插入图片描述

redis在7版本将"ziplist"替换为"listpack"
在这里插入图片描述
在这里插入图片描述

接下来分析"sds"的源码

sds

首先看一下sds的结构
redis-7.2.2\src\sds.h

typedef char * sds;//sds的本质就是char*指向一块连续的内存

sds分为“sdshdr5”、“sdshdr8”、“sdshdr16”、“sdshdr32”、“sdshdr64”五种结构。其中"sdshdr5"从未使用过。

/* Note: sdshdr5 is never used, we just access the flags byte directly.However is here to document the layout of type 5 SDS strings. */
//sdshdr5从未被使用过,只是用它来直接访问flags字节
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; //1byteuint8_t alloc;//1byte,不包括头部和终止符unsigned char flags; /* 3 lsb of type, 5 unused bits  1byte*/char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {uint16_t len; /* used  2byte*/uint16_t alloc; /* excluding the header and null terminator  2byte*/unsigned char flags; /* 3 lsb of type, 5 unused bits  1byte*/char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {uint32_t len; /* used  4byte*/uint32_t alloc; /* excluding the header and null terminator  4byte*/unsigned char flags; /* 3 lsb of type, 5 unused bits  1byte*/char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {uint64_t len; /* used  8byte*/uint64_t alloc; /* excluding the header and null terminator  8byte*/unsigned char flags; /* 3 lsb of type, 5 unused bits  1byte*/char buf[];
};

以上几种结构的布局很相似都是包含"len\alloc\flags\buf"不同的是记录“len\alloc”的字节数不相同。
其中flags的类型如下

#define SDS_TYPE_5  0
#define SDS_TYPE_8  1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4

encoding

sds底层有三种编码方式
redis-7.2.2\src\server.h

#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */

接下来依次看一下编码方式的选择

createStringObject

[redis-7.2.2\src\object.c]

#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 or raw]。长度的阈值为44,为什么是44呢,稍后再做解释。接下来看一下"createEmbeddedStringObject"和"createRawStringObject"

createEmbeddedStringObject

/* Create a string object with encoding OBJ_ENCODING_EMBSTR, that is* an object where the sds string is actually an unmodifiable string* allocated in the same chunk as the object itself. *//*
创建一个底层编码为"OBJ_ENCODING_EMBSTR"的string对象
实际上,"embstr"编码的字符串是不能修改的字符串
和"redisobject"分配在同一个内存块中*/
robj *createEmbeddedStringObject(const char *ptr, size_t len) {//将redisObject和sdshdr8分配在同一个chunk中robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);//这里加1指的是结尾的'\0'字符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;//新创建的字符串的cap和len保持一致sh->flags = SDS_TYPE_8;//const char *SDS_NOINIT = "SDS_NOINIT";//将传入参数ptr指向内存的数据存储到buf中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;
}

总结
  • 编码为embstr的字符串是无法被修改的字符串
  • 对于编码为embstr的字符串使用的sds结构为"sdshdr8"
  • 新创建的字符串的cap和len保持一致,并且字符的结尾含有’\0’结束符[猜想这里应该是为了兼容c中字符数组并且必要时候能够使用c的库函数]
  • 编码为embstr的字符串,redisObject和sds分配在同一个chunk中,如下图所示

在这里插入图片描述

createRawStringObject

/* Create a string object with encoding OBJ_ENCODING_RAW, that is a plain* string object where o->ptr points to a proper sds string. */
//创建一个底层编码为"raw"的字符串,可选择的sds的结构为
//[sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64]
robj *createRawStringObject(const char *ptr, size_t len) {return createObject(OBJ_STRING, sdsnewlen(ptr,len));//createObject根据传入的参数"type、sds"创建新的"redisObject"//sdsnewlen根据传入的参数"char*、size_t"创建新的sds
}
sds sdsnewlen(const void *init, size_t initlen) {return _sdsnewlen(init, initlen, 0);
}/*
根据"init指向的content"和initlen创建一个新的sds
如果init指向的内容是NULL,则将字符串初始化为zero bytes
如果init是SDS_NOINIT,buf将不会被初始化
*/
/*
The string is always null-terminated (all the sds strings are, always) 
so even if you create an sds string with:
mystring = sdsnewlen("abc",3);
You can print the string with printf() 
as there is an implicit \0 at the end of the string.
However the string is binary safe and can contain\0 
characters in the middle, 
as the length is stored in the sds header.将上述一段英文翻译成中文:
所有的sds string都是以空字符即'\0'结尾;
用如下的方式创建一个新的sds string:
mystring = sdsnewlen("abc",3);
可以使用"printf()"函数打印mystring,因为mystring的结尾有个隐式的'\0'字符。
但是mystring是二进制安全的并且可以在在字符串的中间包含'\0'的字符,
因为字符串的长度会被记录在sds的header中。
*/
sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {void *sh;sds s;//根据initlen大小获取sds的结构类型char type = sdsReqType(initlen);/* Empty strings are usually created in order to append. Use type 8* since type 5 is not good at this. */if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;//获取头部大小int hdrlen = sdsHdrSize(type);unsigned char *fp; /* flags pointer. */size_t usable;assert(initlen + hdrlen + 1 > initlen); /* Catch size_t overflow *//*Allocate memory or panic.usable is set to the usable size if non NULL. 如果能够成功分配内存,那么usable=hdrlen+initlen+1*/sh = trymalloc?s_trymalloc_usable(hdrlen+initlen+1, &usable) :s_malloc_usable(hdrlen+initlen+1, &usable);if (sh == NULL) return NULL;if (init==SDS_NOINIT)init = NULL;else if (!init)memset(sh, 0, hdrlen+initlen+1);s = (char*)sh+hdrlen;fp = ((unsigned char*)s)-1;//正常情况下,usable和initlen保持一致usable = usable-hdrlen-1;if (usable > sdsTypeMaxSize(type))usable = sdsTypeMaxSize(type);switch(type) {case SDS_TYPE_5: {*fp = type | (initlen << SDS_TYPE_BITS);break;}case SDS_TYPE_8: {SDS_HDR_VAR(8,s);sh->len = initlen;sh->alloc = usable;*fp = type;break;}case SDS_TYPE_16: {SDS_HDR_VAR(16,s);sh->len = initlen;sh->alloc = usable;*fp = type;break;}case SDS_TYPE_32: {SDS_HDR_VAR(32,s);sh->len = initlen;sh->alloc = usable;*fp = type;break;}case SDS_TYPE_64: {SDS_HDR_VAR(64,s);sh->len = initlen;sh->alloc = usable;*fp = type;break;}}//将init指向的内容复制到buf中if (initlen && init)memcpy(s, init, initlen);s[initlen] = '\0';return s;
}
robj *createObject(int type, void *ptr) {
//ptr指向创建好的sds
//为redisObject分配一块新的内存空间,由此也可以得出底层编码为"raw"的redisObject和sds分开存储属于两块不同的连续内存块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"的string是可以修改的,编码为"embstr"的string若想要修改需要先将其的编码变为"raw"才可以被修改。如下图所示
    在这里插入图片描述

  • 根据传入的字符串的initlen从[sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr64]选择合适的sds头部结构

  • 所有的字符串都以’\0’作为结束符,可以使用printf()函数进行打印;二进制安全的,字符串中间可以存储’\0’字符,长度信息存储在sds_header中

  • 编码为"raw"的字符串,redisObject和sds存储在不同的内存块中,如下图所示
    在这里插入图片描述

createStringObjectFromLongDouble

#define MAX_LONG_DOUBLE_CHARS 5*1024/*从长双精度类型创建字符串对象。如果humanfriendly为非零,则不使用指数格式并在末尾修剪尾随的零,但是这会导致精度的损失。否则使用exp格式,并且不修改snprintf()的输出。“humanfriendly”选项用于INCRBYFLOAT和HINCRBYFLOAT。*/
robj *createStringObjectFromLongDouble(long double value, int humanfriendly) {char buf[MAX_LONG_DOUBLE_CHARS];int len = ld2string(buf,sizeof(buf),value,humanfriendly? LD_STR_HUMAN: LD_STR_AUTO);return createStringObject(buf,len);
}

在这里插入图片描述

总结

从以上代码可以看出“long double”类型的浮点数以“string”的形式存储,底层的编码方式为"embstr"或者“raw”

createStringObjectFromLongLongWithOptions

/* Create a string object from a long long value according to the specified flag. */
#define LL2STROBJ_AUTO 0       /* automatically create the optimal string object */
#define LL2STROBJ_NO_SHARED 1  /* disallow shared objects */
#define LL2STROBJ_NO_INT_ENC 2 /* disallow integer encoded objects. */
robj *createStringObjectFromLongLongWithOptions(long long value, int flag) {robj *o;//#define OBJ_SHARED_INTEGERS 10000if (value >= 0 && value < OBJ_SHARED_INTEGERS && flag == LL2STROBJ_AUTO) {//value值处于[0,10000)进行共享o = shared.integers[value];} else {//在long的范围内,用int进行编码if ((value >= LONG_MIN && value <= LONG_MAX) && flag != LL2STROBJ_NO_INT_ENC) {o = createObject(OBJ_STRING, NULL);o->encoding = OBJ_ENCODING_INT;//value值内嵌在在ptr中o->ptr = (void*)((long)value);} else {//超出long表示的范围则根据长度在"embstr or raw"中选择编码char buf[LONG_STR_SIZE];int len = ll2string(buf, sizeof(buf), value);o = createStringObject(buf, len);}}return o;
}

总结

  • value值在[0,10000)内共享redisobject
    在这里插入图片描述

  • value在long范围内,底层编码为int。并且value值内嵌在ptr中
    在这里插入图片描述

  • value不在long范围内,根据len的长度选择合适的编码,“embstr"或者"raw”

相关操作

redis-7.2.2\src\sds.c
通过以上源码的分析,我们发现不论字符串以何种编码方式创建。sds的cap都和len保持一致,这是为了节约内存,因为绝大多数情况下都不会使用"append"操作。
接下来就看一下"append"操作的流程,

sdscatlen


/*
将t指向的字符串拼接到sds指向的字符串的末尾,
拼接之后,传入的sds不再有效,需要使用返回的新sds代替旧的传入的sds
*/
sds sdscatlen(sds s, const void *t, size_t len) {//当前sds指向字符串的长度size_t curlen = sdslen(s);//通过sdsMakeRoomFor计算拼接之后新的字符串的长度并返回新的sdss = sdsMakeRoomFor(s,len);//没有足够的空间,拼接失败if (s == NULL) return NULL;//将t指向的字符串拼接到s的末尾,s+curlen存储的是'\0'结尾符,也即是t指向的字符串会覆盖sds指向字符串的'\0'结尾符memcpy(s+curlen, t, len);//设置新sds的长度sdssetlen(s, curlen+len);//加上'\0'结尾符s[curlen+len] = '\0';return s;
}

接下来看一下如何"扩容"的

/*
为sds分配更多的空间避免重复的进行append操作
*/
sds sdsMakeRoomFor(sds s, size_t addlen) {return _sdsMakeRoomFor(s, addlen, 1);
}
/* Enlarge the free space at the end of the sds string so that the caller* is sure that after calling this function can overwrite up to addlen* bytes after the end of the string, plus one more byte for null term.* If there's already sufficient free space, this function returns without any* action, if there isn't sufficient free space, it'll allocate what's missing,* and possibly more:* When greedy is 1, enlarge more than needed, to avoid need for future reallocs* on incremental growth.* When greedy is 0, enlarge just enough so that there's free space for 'addlen'.** Note: this does not change the *length* of the sds string as returned* by sdslen(), but only the free buffer space we have. */
/*
扩大sds字符串末尾的可用空间,以便调用者在调用此函数后可以覆盖原字符串的addlen字节和'\0'字符
如果已经有足够的空闲空间,这个函数返回时不做任何操作,
如果没有足够的空闲空间,它将分配缺失的部分,甚至更多:
当greedy为1时,分配比需要的更多,以避免再次扩容时需要重新分配。
当greedy为0时,将其放大到足够大以便为addlen腾出空间即可。
注意:这不会改变sdslen()返回的sds字符串的长度,而只会改变我们拥有的空闲缓冲区空间也即是alloc。
*/
sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {void *sh, *newsh;//获取旧sds的可用space,[s->alloc - s->len];size_t avail = sdsavail(s);size_t len, newlen, reqlen;char type, oldtype = s[-1] & SDS_TYPE_MASK;int hdrlen;size_t usable;//可用空闲空间满足要求直接返回,什么也不做if (avail >= addlen) return s;//获取旧sds字符串的长度lenlen = sdslen(s);sh = (char*)s-sdsHdrSize(oldtype);//拼接之后新字符串的长度reqlen = newlen = (len+addlen);assert(newlen > len);   /* Catch size_t overflow *///greedy=1表示需要预先多分配一些内存if (greedy == 1) {//#define SDS_MAX_PREALLOC (1024*1024) [1MB]//newlen小于1MB,翻倍扩容if (newlen < SDS_MAX_PREALLOC)newlen *= 2;else//需要的长度大于等于1MB,多扩容1MBnewlen += SDS_MAX_PREALLOC;}//根据newlen计算需要使用的sds类型type = sdsReqType(newlen);if (type == SDS_TYPE_5) type = SDS_TYPE_8;hdrlen = sdsHdrSize(type);//多分配1byte是为了存储末尾的'\0'字符assert(hdrlen + newlen + 1 > reqlen);  /* Catch size_t overflow *///sds头部类型不变,在原地扩容内存if (oldtype==type) {newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);if (newsh == NULL) return NULL;//获取新sds的bufs = (char*)newsh+hdrlen;} else {//sds头部类型改变,重新分配内存newsh = s_malloc_usable(hdrlen+newlen+1, &usable);if (newsh == NULL) return NULL;//拷贝旧sds中的内容到新的sds中memcpy((char*)newsh+hdrlen, s, len+1);s_free(sh);s = (char*)newsh+hdrlen;s[-1] = type;//设置lensdssetlen(s, len);}//计算空闲空间usable = usable-hdrlen-1;if (usable > sdsTypeMaxSize(type))usable = sdsTypeMaxSize(type);//设置allocsdssetalloc(s, usable);return s;
}

总结

在这里插入图片描述

阈值44

接下来分析一下为什么编码"embstr"和编码"raw"的长度界限值为44
先来回顾一下"redisObject"的样子

struct redisObject {unsigned type:4;//表示数据结构的类型,占4bitsunsigned encoding:4;//数据结构底层使用的编码,占4bitsunsigned lru:LRU_BITS; //用于LRU或者LFU内存淘汰算法,占24bitsint refcount;//引用计数,占4bytes,引用计数变为0对象被销毁,内存被回收void *ptr;//指向具体的数据,占8bytes
};

通过上述代码,可以计算出redisObject所占用的内存大小为:

4bits+4bits+24bits+4bytes+8bytes=16bytes

通过"createEmbeddedStringObject"的源码分析,发现"embstr"编码的字符串使用的sds结构为sdshdr8

struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len; //1byteuint8_t alloc; //1byteunsigned char flags; //1bytechar buf[];
};

通过上述代码,可以计算出"sds"中除了存储数据的buf,其余所占用的内存空间大小为:

1byte+1byte+1byte=3bytes

通过对"“和”"的源码分析发现,不论哪种编码[embstr or raw]的字符串都是以’\0’作为结束字符的,所以还需要占用额外的1byte存储结尾的标识符。

整体计算下来,一个完整的字符串除了存储的数据所占用的内存空间大小为:

16bytes+3bytes+1byte=20bytes

而,redis的内存分配器"jemalloc\tcmalloc"等分配内存大小的单位是"2\4\8\16\32\64"字节等,为了能容纳一个完整的"embstr"字符串,至少会分配32字节的内存,至多会分配64字节的内存。如果64字节不够存储的话,redis就将其视为一个大字符串,不再使用"embstr"进行编码,而是采用"raw"进行编码。

所以"embstr"和"raw"之间的阈值为44字节

64bytes-20bytes=44bytes

sds VS C字符串

redis规定字符串的长度为"512MB"。
sds的本质还是char *,但是相比与"C字符串"多了一些字段"len,alloc,flag",优势如下

  • 以O(1)的时间复杂度快速获取字符换的长度。因为字符串的长度会记录在sds的header中。
  • 二进制安全。不再以’\0’字符作为字符串的结束标志,而是记录了字符串的长度,并且可以在字符串的中间位置存储’\0’字符。同样可以使用c相关函数。
  • 不同的数据对应不同的底层编码,更加灵活,更加节省内存。

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

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

相关文章

docker镜像的生成过程

镜像的生成过程 Docker镜像的构建过程&#xff0c;大量应用了镜像间的父子关系。即下层镜像是作为上层镜像的父镜像出现的&#xff0c;下层镜像是作为上层镜像的输入出现。上层镜像是在下层镜像的基础之上变化而来。 FROM centos:7 FROM指令是Dockerfile中唯一不可缺少的命令&a…

Web APIs知识点讲解

学习目标: 能获取DOM元素并修改元素属性具备利用定时器间歇函数制作焦点图切换的能力 一.Web API 基本认知 1.作用和分类 作用: 就是使用 JS 去操作 html 和浏览器分类&#xff1a;DOM (文档对象模型)、BOM&#xff08;浏览器对象模型&#xff09; 2.DOM DOM(Document Ob…

吉林大学19、21级计算机学院《计算机网络》期末真题试题

一、21级&#xff08;考后回忆&#xff09; 一、不定项选择&#xff08;一共10个选择题&#xff0c;一个两分&#xff0c;选全得满分&#xff09; 不定项&#xff1a;可以选择1~4个 考点有&#xff1a; ①协议、服务 ②码分多路复用通过接受码片序列&#xff0c;求哪个站点发送…

vue3项目中axios的常见用法和封装拦截(详细解释)

1、axios的简单介绍 Axios是一个基于Promise的HTTP客户端库&#xff0c;用于浏览器和Node.js环境中发送HTTP请求。它提供了一种简单、易用且功能丰富的方式来与后端服务器进行通信。能够发送常见的HTTP请求&#xff0c;并获得服务端返回的数据。 此外&#xff0c;Axios还提供…

C++ queue

目录 一、介绍 二、queue使用 三、模拟实现 四、优先级队列 五、priority_queue使用 OJ题&#xff1a;215. 数组中的第K个最大元素 快速排序 优先级队列 TOPK 六、模拟实现priority_queue 1、仿函数 2、优先级队列类 3、测试函数 一、介绍 1、队列是一种容器适配器…

【手搓深度学习算法】用线性回归预测波士顿房价

线性回归 线性回归是一种监督学习方法&#xff0c;用于建立因变量与一个或多个自变量之间的关系。线性回归的目标是找到一条直线&#xff0c;使得所有数据点到这条直线的距离之和最小。 线性回归的基本形式如下&#xff1a; y β 0 β 1 x 1 β 2 x 2 . . . β n x n ϵ…

mysql基础-常用函数汇总

目录 1. 查询技巧 2. 时间函数 2.1 now() 2.2 current_date() 2.3 时间差timestampdiff&#xff08;&#xff09;与datediff&#xff08;&#xff09; 2.4 其他时间函数 3. 字符函数 3.1 截取函数 3.2 分割函数 3.3 left与right函数 3.4 其他函数 4. 数字函数 5. …

自定义HBase负载均衡器MyCustomBalancer实现步骤与代码解析

目录 1.HBase默认负载均衡策略 1.1 负载均衡总体流程 1.2 不能触发负载均衡的情况 1.3 负载均衡算法 2.自定义的 HBase 负载均衡器的步骤 3.MyCustomBalancer的代码细节 3.1 balanceCluster 方法的作用 3.2balanceCluster 对数据的影响 3.3监控HBase的性能指标 3.3.…

在国内 PMP 有多少含金量?

在我国大陆&#xff0c;有好多证书被商业化得太重了&#xff0c;甚至演变成了个人或一些公司摇钱的工具。所以有些证书受人吹捧它崛起的快&#xff0c;但是活不长&#xff0c;甚至“夭折”&#xff0c;比如以前微软系列的证书&#xff1b; 而PMP认证从国外引进大陆这么多年了&…

PMP认证考试详细备考攻略,全是干货!

要明白&#xff0c;虽然PMP备考考试只是一时的过程&#xff0c;但通过PMP获得的证书和能力是永久的。 这不仅仅是因为我拿到了PMP培训结业证书和PMP认证证书这两个证明&#xff0c;更重要的是在参加PMP认证考试的整个过程中&#xff0c;我学到了很多关于项目管理的知识&#x…

Python基础入门第九课笔记(文件和文件夹)

1&#xff0c;新建文本并且写内容 a open(1.text,w) a.write("""aaa bbb ccc""") a.close() 2,seek( )移动文件指针 文件对象.seek(偏移量&#xff0c;起始位置) # 起始位置&#xff1a;0开头&#xff0c;1当前位置&#xff0c;2文件结尾…

获取深层次字段报错TypeError: Cannot read properties of undefined (reading ‘title‘)

动态生成菜单时报错,不能多层获取路由meta下面的title字段 <template><p>{{ meneList }}</p><template v-for"item in meneList" :key"item.path"><el-menu-item v-if"!item.children"><template #title>{…

一键了解获取网页requests方式

目录 一、爬虫原理&#xff1a; 二、安装&#xff1a; 测试&#xff1a; 三、文件的操作 方式一 方式二: 方式三 四、认识User-Agent 4.1、为什么用User-Agent&#xff1a; 步骤&#xff1a; 五、请求方式 5.1、get 5.2、post 六、爬出有中国关键字页面案例 一、爬…

小型图书借阅管理系统

springbootmybatismysqlthymeleafjquery构建的小型图书借阅管理系统后端 1.springboot 2.mybatis数据库 1.mysql前端 1.jquery 2.jquery-validate 3.htmlcss

【性能测试入门】:压力测试概念!

压力测试可以验证软件应用程序的稳定性和可靠性。压力测试的目标是评估软件在极端负载条件下的鲁棒性和错误处理能力&#xff0c;并确保软件在紧急情况下不会崩溃。它甚至可以进行超出软件正常工作条件的测试&#xff0c;并评估软件在极端条件下的工作方式。 在软件工程中&…

Linux 上 Nginx 配置访问 web 服务器及配置 https 访问配置过程记录

目录 一、前言说明二、配置思路三、开始修改配置四、结尾 一、前言说明 最近自己搭建了个 Blog 网站&#xff0c;想把网站部署到服务器上面&#xff0c;本文记录一下搭建过程中 Nginx 配置请求转发的过程。 二、配置思路 web项目已经在服务器上面运行起来了&#xff0c;运行的端…

WPS使用技巧——默认粘贴无格式文本

从网页或者其他文档内复制的文本往往带有原本的格式&#xff0c;粘贴到自己的word文档里面&#xff0c;要么先粘贴后统一格式&#xff0c;要么右键选择“只粘贴文本”&#xff0c;非常不便。 今天分享一个可以将粘贴方式默认为“只粘贴文本”的无格式粘贴方法&#xff0c;这样…

pycharm的使用技巧

1.新建文件时,自动生成代码 settings->editor->file and code templates,选择python script ${NAME} 文件名 ${DATE} 日期 2.自动补齐自定义段落 settings->editor->live templates,在右侧点击+号,添加自定义的内容 完成之后,在下方勾选python 3.修改注释的…

(23)Linux的软硬连接

前言&#xff1a;上一章我们讲解了 inode&#xff0c;为文件系统收了尾&#xff0c;这几章我们充分地讲解完了文件系统的知识点&#xff0c;现在我们开始开始学习软硬链接了。 软硬链接 1、Linux 下的快捷方式&#xff1a;软链接 上一章我们介绍完了 inode &#xff0c;我们…

【C语言】Linux实现高并发处理的过程

一、实现高并发的几种策略 C语言本身并没有内建的多线程支持&#xff08;新版C语言支持&#xff0c;但用得不多&#xff09;&#xff0c;但是在多数操作系统中&#xff0c;可以使用库来实现多线程编程。例如&#xff0c;在POSIX兼容系统上&#xff0c;可以使用 pthreads 库来创…