快速列表quicklist

目录

为什么使用快速列表quicklist

对比双向链表

对比压缩列表ziplist

quicklist结构

节点结构quicklistNode

quicklist 

管理ziplist信息的结构quicklistEntry

 迭代器结构quicklistIter

 quicklist的API

1.创建快速列表

2.创建快速列表节点

3.头插quicklistPushHead 和尾插quicklistPushTail

4.特定位置插入元素(不是节点)

5.删除元素 

6.查找元素

总结 quicklist的特性


为什么使用快速列表quicklist

​在 Redis 的早期设计中,如果列表类型的对象中元素的长度较小或数量比较少的,就采用压缩列表来存储,反之则使用双向链表

对比双向链表

双向链表便于在链表的两端进行插入和删除操作,在插入节点上复杂度很低,但是它的内存开销比较大,每个节点除了要保存数据之外,还要额外保存两个指针,并且双向链表的各个节点是单独的内存块,地址不连续,容易产生内存碎片

对比压缩列表ziplist

压缩列表存储在一段连续的内存上,所以存储效率高。但是,它每次变更的时间复杂度都比较高,插入和删除操作需要频繁的申请和释放内存,如果压缩列表长度很长,一次 realloc 可能会导致大批量的数据拷贝。

如何保留ziplist的空间高效性,又能不让其更新复杂度过高?

​Redis 在 3.2 版本之后引入了快速列表,列表类型的对象其底层都是由快速列表实现。快速列表是双向链表和压缩列表的混合体,它将双向链表按段切分,每一段都使用压缩列表来紧凑存储,多个压缩列表之间使用双向指针关联起来。

quicklist结构

quicklist是个双端链表,节点结构是quicklistNode,节点的zl字段指向压缩列表。

节点结构quicklistNode

quicklistNode是快速列表的节点。

typedef struct quicklistNode {struct quicklistNode *prev;struct quicklistNode *next;unsigned char *zl;        //指向ziplistunsigned int sz;             /* ziplist size in bytes */unsigned int count : 16;     /* count of items in ziplist */unsigned int encoding : 2;   /* RAW==1 or LZF==2 */unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */unsigned int recompress : 1; /* was this node previous compressed? */unsigned int attempted_compress : 1; /* node can't compress; too small */unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;
  • prev:前驱节点指针。
  • next:后驱节点指针。
  • zl:数据指针,如果当前节点的数据没有压缩,则指向一个 ziplist 结构,否则指向一个 quicklistLZF 结构。
  • sz:表示zl指向的数据总大小。注意,若数据被压缩,其表示压缩前的数据长度大小。
  • count:占16bit,表示当前节点的ziplist的entry的个数
  • encoding:占2bit,表示当前节点的数据是否被压缩。1表示没有压缩;2是压缩,用的是LZF算法。
  • container:是一个预留字段,本来设计是用来表明一个quicklist节点下面是直接存数据,还是使用ziplist存数据,或者用其它的结构来存数据(用作数据容器,所以叫container)。目前这个值是一个固定的值2,表示使用 ziplist 作为数据容器
  • recompress:当我们使用类似 lindex 这样的命令查看了某一项本来压缩的数据时,需要把数据暂时解压,这时就设置 recompress=1 做一个标记,等有机会再把数据重新压缩。
  • extra:其它扩展字段。目前Redis的实现里也没用上。

quicklist 

快速列表的结构,从其结构可以看出其是一个链表,保存了头尾节点。

#if UINTPTR_MAX == 0xffffffff
/* 32-bit */
#   define QL_FILL_BITS 14
#   define QL_COMP_BITS 14
#   define QL_BM_BITS 4
#elif UINTPTR_MAX == 0xffffffffffffffff
/* 64-bit */
#   define QL_FILL_BITS 16
#   define QL_COMP_BITS 16
#   define QL_BM_BITS 4 /* we can encode more, but we rather limit the usersince they cause performance degradation. */
#else
#   error unknown arch bits count
#endif
//上面的表示:QL_FILL_BITS值在32位机器上是14,64位机器上是16typedef struct quicklist {quicklistNode *head;        //头节点quicklistNode *tail;        //尾结点unsigned long count;        /* total count of all entries in all ziplists */unsigned long len;          /* number of quicklistNodes */int fill : QL_FILL_BITS;              /* fill factor for individual nodes */unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */unsigned int bookmark_count: QL_BM_BITS;quicklistBookmark bookmarks[];
} quicklist;//当指定使用lzf压缩算法压缩ziplist的entry节点时,quicklistNode结构的zl成员指向quicklistLZF结构
typedef struct quicklistLZF {//表示被LZF算法压缩后的ziplist的大小unsigned int sz; /* LZF size in bytes*///保存压缩后的ziplist的数组,柔性数组char compressed[];
} quicklistLZF;/* quicklist node encodings */
#define QUICKLIST_NODE_ENCODING_RAW 1
#define QUICKLIST_NODE_ENCODING_LZF 2/* quicklist compression disable */
#define QUICKLIST_NOCOMPRESS 0/* quicklist container formats */
#define QUICKLIST_NODE_CONTAINER_NONE 1
#define QUICKLIST_NODE_CONTAINER_ZIPLIST 2#define quicklistNodeIsCompressed(node)                                        \((node)->encoding == QUICKLIST_NODE_ENCODING_LZF)
  • count:所有压缩列表的节点数量之和
  • len:快速类别的节点数量
  • fill:存放 list-max-ziplist-size 参数的值,用于设置每个quicklistnode的压缩列表的所有entry的总长度大小。其值默认是-2,表示每个quicklistNode节点的ziplist所占字节数不能超过8kb。若是任意正数:,表示ziplist结构所最多包含的entry个数,最大为215215。
  • compress:存放 list-compress-depth 参数的值,用于设置压缩深度。快速列表默认的压缩深度为 0,即不压缩。为了支持快速的 push/pop 操作,快速列表的首尾两个节点不压缩,此时压缩深度就是1。若压缩深度为2,表示快速列表的首尾第一个及第二个节点都不压缩。
  • bookmark_count:占 4 bit,bookmarks 数组的长度。 
  • bookmarks:这是一个可选字段,快速列表重新分配内存时使用,不使用时不占用空间。

管理ziplist信息的结构quicklistEntry

和压缩列表一样,entry结构在储存时是一连串的内存块,需要将其每个entry节点的信息读取到管理该信息的结构体中,以便操作。

//管理quicklist中quicklistNode节点中ziplist信息的结构
typedef struct quicklistEntry {const quicklist *quicklist;   //指向所属的quicklist的指针quicklistNode *node;          //指向所属的quicklistNode节点的指针unsigned char *zi;            //指向当前ziplist结构的指针unsigned char *value;         //查找到的元素如果是字符串,则存在value字段long long longval;            //查找到的元素如果是整数,则存在longval字段unsigned int sz;              //保存当前元素的长度int offset;                  //保存查找到的元素距离压缩列表头部/尾部隔了多少个节点
} quicklistEntry;

迭代器结构quicklistIter

在Redis的quicklist结构中,实现了自己的迭代器,用于遍历节点。

//quicklist的迭代器结构
typedef struct quicklistIter {const quicklist *quicklist;   //指向所属的quicklist的指针quicklistNode *current;       //指向当前迭代的quicklist节点的指针unsigned char *zi;            //指向当前quicklist节点中迭代的ziplistlong offset;                  //当前ziplist结构中的偏移量     int direction;                //迭代方向
} quicklistIter;

 quicklist的API

1.创建快速列表

创建快速列表,快速列表的成员变量都使用默认值

/* Create a new quicklist.* Free with quicklistRelease(). */
//创建快速列表,并对各个字段进行初始化
quicklist *quicklistCreate(void) {struct quicklist *quicklist;quicklist = zmalloc(sizeof(*quicklist));quicklist->head = quicklist->tail = NULL;quicklist->len = 0;quicklist->count = 0;quicklist->compress = 0;quicklist->fill = -2;quicklist->bookmark_count = 0;return quicklist;
}

创建列表,传入自己设置的参数

//设置压缩深度
#define COMPRESS_MAX ((1 << QL_COMP_BITS)-1)
void quicklistSetCompressDepth(quicklist *quicklist, int compress) {if (compress > COMPRESS_MAX) {compress = COMPRESS_MAX;} else if (compress < 0) {compress = 0;}quicklist->compress = compress;
}//设置压缩列表的大小(成员fill),即是每个压缩列表的总entry的总长度大小
#define FILL_MAX ((1 << (QL_FILL_BITS-1))-1)
void quicklistSetFill(quicklist *quicklist, int fill) {if (fill > FILL_MAX) {fill = FILL_MAX;} else if (fill < -5) {fill = -5;}quicklist->fill = fill;
}//设置快速列表的参数
void quicklistSetOptions(quicklist *quicklist, int fill, int depth) {quicklistSetFill(quicklist, fill);quicklistSetCompressDepth(quicklist, depth);
}//通过一些默认参数创建快速列表,就是调用了前面这些封装好的函数
quicklist *quicklistNew(int fill, int compress) {quicklist *quicklist = quicklistCreate();quicklistSetOptions(quicklist, fill, compress);return quicklist;
}

2.创建快速列表节点

REDIS_STATIC quicklistNode *quicklistCreateNode(void) {quicklistNode *node;node = zmalloc(sizeof(*node));node->zl = NULL;node->count = 0;node->sz = 0;node->next = node->prev = NULL;node->encoding = QUICKLIST_NODE_ENCODING_RAW;    //默认不压缩node->container = QUICKLIST_NODE_CONTAINER_ZIPLIST; //默认使用压缩列表结构来存数据node->recompress = 0;return node;
}

3.头插quicklistPushHead 和尾插quicklistPushTail

/* 头部插入* 如果在已存在节点插入,返回0* 如果是在新的头结点插入,返回1 */
int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {quicklistNode *orig_head = quicklist->head;//判断头节点的空间是否足够容纳要添加的元素if (likely(_quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {quicklist->head->zl =ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);  // 在头结点对应的ziplist中插入 quicklistNodeUpdateSz(quicklist->head);} else { // 否则新建一个头结点,然后插入数据 quicklistNode *node = quicklistCreateNode();node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);quicklistNodeUpdateSz(node);//新增元素添加进这个新的快速列表节点里_quicklistInsertNodeBefore(quicklist, quicklist->head, node);}quicklist->count++;quicklist->head->count++;return (orig_head != quicklist->head);
}/* 尾部插入。* 如果在已存在节点插入,返回0* 如果是在新的头结点插入,返回1 */
int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {quicklistNode *orig_tail = quicklist->tail;if (likely(_quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {quicklist->tail->zl =ziplistPush(quicklist->tail->zl, value, sz, ZIPLIST_TAIL);quicklistNodeUpdateSz(quicklist->tail);} else {quicklistNode *node = quicklistCreateNode();node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_TAIL);quicklistNodeUpdateSz(node);_quicklistInsertNodeAfter(quicklist, quicklist->tail, node);}quicklist->count++;quicklist->tail->count++;return (orig_tail != quicklist->tail);
}

头插和尾插都是先调用了_quicklistNodeAllowInsert来判断能否在当前头/尾节点插入。如果能插入就直接插入到对应的ziplist中,否则就需要新建一个新节点再进行操作。

前面讲解过的quicklist结构的fill字段,其实_quicklistNodeAllowInsert就是根据fill的值来判断是否已经超过最大容量的。

其中使用到函数_quicklistInsertNodeBefore 和 _quicklistInsertNodeBefore,这两个就是在指定位置插入元素。

4.特定位置插入元素(不是节点)

注意:我们使用Redis,接触到的快速列表插入的都是插入元素,不是插入快速列表的节点。

插入元素会使用到结构体quicklistEntry

void quicklistInsertBefore(quicklist *quicklist, quicklistEntry *entry,void *value, const size_t sz) {_quicklistInsert(quicklist, entry, value, sz, 0);
}void quicklistInsertAfter(quicklist *quicklist, quicklistEntry *entry,void *value, const size_t sz) {_quicklistInsert(quicklist, entry, value, sz, 1);
}/* 在一个已经存在的entry前面或者后面插入一个新的entry * 如果after==1表示插入到后面,否则是插入到前面  */
REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry,void *value, const size_t sz, int after) {int full = 0, at_tail = 0, at_head = 0, full_next = 0, full_prev = 0;int fill = quicklist->fill;//1. 获取插入位置的quicklist的节点,通过entry的node字段quicklistNode *node = entry->node;quicklistNode *new_node = NULL;assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) *///2. 该节点不存在,也只有当快速列表的头或尾节点存在才会进入这个if条件if (!node) {/* we have no reference node, so let's create only node in the list */D("No node given!");new_node = quicklistCreateNode();//将添加的元素插入快速列表节点对应的压缩列表的节点头部new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);__quicklistInsertNode(quicklist, NULL, new_node, after);new_node->count++;quicklist->count++;return;}//3. 判断node节点对应的压缩列表是否能够容纳得下要添加的元素,即node节点是否已满if (!_quicklistNodeAllowInsert(node, fill, sz)) {D("Current node is full with count %d with requested fill %lu",node->count, fill);//表示当前节点的数量已满full = 1;}//4. 判断是否需要是在尾部添加if (after && (entry->offset == node->count)) {//表示在尾部添加at_tail = 1;if (!_quicklistNodeAllowInsert(node->next, fill, sz)) {D("Next node is full too.");//表示下一quicklistNode的ziplist的entry已满了full_next = 1;}}//5.判断是否在头部添加if (!after && (entry->offset == 0)) {D("At Head");at_head = 1;if (!_quicklistNodeAllowInsert(node->prev, fill, sz)) {D("Prev node is full too.");//表示前一节点已满full_prev = 1;}}//未完待续..........,后面再讲解
}
  • 1.通过通过entry的node字段获取插入位置的quicklist的节点
  • 2.判断该节点是否存在,若不存在,就创建节点,并创建ziplist,插入该元素到ziplist
  • 3.判断node节点对应的压缩列表是否能够容纳得下要添加的元素,即node节点是否已满,用来设置变量full
  • 4.判断是否在该quicklistNode的ziplist的尾部添加,并判断该quicklistNode的下一节点的ziplist是否已满
  • 5.判断是否在该quicklistNode的ziplist的头部添加,并判断该quicklistNode的前驱节点的ziplist是否已满
REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry,void *value, const size_t sz, int after) {//........................................../* Now determine where and how to insert the new element */if (!full && after) {//6. 当前节点的zipList不满,并且是在当前位置的后面插入D("Not full, inserting after current position.");quicklistDecompressNodeForUse(node);    //当前节点解压缩//entry->zi是ziplist的一个entry,返回entry->zi的下一个ziplist的entryunsigned char *next = ziplistNext(node->zl, entry->zi);if (next == NULL) {node->zl = ziplistPush(node->zl, value, sz, ZIPLIST_TAIL);} else {node->zl = ziplistInsert(node->zl, next, value, sz);}node->count++;quicklistNodeUpdateSz(node);//添加完元素后再根据node->recompress判断是否对压缩列表进行压缩quicklistRecompressOnly(quicklist, node);} else if (!full && !after) {//7. 当前节点的ziplist不满,在当前entry的前面插入D("Not full, inserting before current position.");quicklistDecompressNodeForUse(node);node->zl = ziplistInsert(node->zl, entry->zi, value, sz);node->count++;quicklistNodeUpdateSz(node);quicklistRecompressOnly(quicklist, node);} else if (full && at_tail && node->next && !full_next && after) {/* If we are: at tail, next has free space, and inserting after:*   - insert entry at head of next node. *///8. D("Full and tail, but next isn't full; inserting next node head");new_node = node->next;quicklistDecompressNodeForUse(new_node);new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_HEAD);new_node->count++;quicklistNodeUpdateSz(new_node);quicklistRecompressOnly(quicklist, new_node);} else if (full && at_head && node->prev && !full_prev && !after) {/* If we are: at head, previous has free space, and inserting before:*   - insert entry at tail of previous node. *///9. D("Full and head, but prev isn't full, inserting prev node tail");new_node = node->prev;quicklistDecompressNodeForUse(new_node);new_node->zl = ziplistPush(new_node->zl, value, sz, ZIPLIST_TAIL);new_node->count++;quicklistNodeUpdateSz(new_node);quicklistRecompressOnly(quicklist, new_node);} else if (full && ((at_tail && node->next && full_next && after) ||(at_head && node->prev && full_prev && !after))) {/* If we are: full, and our prev/next is full, then:*   - create new node and attach to quicklist *///10. D("\tprovisioning new node...");new_node = quicklistCreateNode();new_node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);new_node->count++;quicklistNodeUpdateSz(new_node);__quicklistInsertNode(quicklist, node, new_node, after);} else if (full) {/* else, node is full we need to split it. *//* covers both after and !after cases */D("\tsplitting node...");//11quicklistDecompressNodeForUse(node);new_node = _quicklistSplitNode(node, entry->offset, after);new_node->zl = ziplistPush(new_node->zl, value, sz,after ? ZIPLIST_HEAD : ZIPLIST_TAIL);new_node->count++;quicklistNodeUpdateSz(new_node);__quicklistInsertNode(quicklist, node, new_node, after);_quicklistMergeNodes(quicklist, node);}quicklist->count++;
}

这部分主要是分了几种情况来插入:

  • 6.当前节点的ziplist没满,并在当前entry的后面插入
  • 7.当前节点的ziplist没满,并在当前entry的前面插入
  • 8.当前节点的ziplist已满、要添加在尾部、并且后移节点是存在的、后一节点的ziplist没满,那就添加到后一节点对应的ziplist的第一个entry的前面
  • 9.当前节点的ziplist已满、要添加在头部、并且前一节点存在、前一节点的ziplist没满,就添加到前一节点的ziplist的尾部
  • 10.当前节点的ziplist已满、插入的位置是在头/尾的、并且当前节点的前/后节点的ziplist已满,则需要创建新的quicklistNode来存放要放的元素。
  • 11.当前节点的ziplist已满,但是插入的位置不在两端的,则要从插入位置把当前节点分裂成两个节点

5.删除元素 

 快速列表删除元素有两个函数 quicklistDelEntry 和 quicklistDelRange,分别是删除单个元素删除某个区间的元素。

删除元素使用到了迭代器结构quicklistIter,需要更新迭代器对应的节点等信息。

/* Delete one element represented by 'entry'*/
void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) {quicklistNode *prev = entry->node->prev;quicklistNode *next = entry->node->next;//删除元素,返回值 deleted_node 表示当前节点是否要删除。1表示该节点已删除int deleted_node = quicklistDelIndex((quicklist *)entry->quicklist,entry->node, &entry->zi);/* after delete, the zi is now invalid for any future usage. */iter->zi = NULL;/* If current node is deleted, we must update iterator node and offset. */if (deleted_node) {if (iter->direction == AL_START_HEAD) {iter->current = next;iter->offset = 0;} else if (iter->direction == AL_START_TAIL) {iter->current = prev;iter->offset = -1;}}
}REDIS_STATIC int quicklistDelIndex(quicklist *quicklist, quicklistNode *node,unsigned char **p) {int gone = 0;//删除 node 节点对应的压缩列表 p 位置的entry,返回新的zipListnode->zl = ziplistDelete(node->zl, p);node->count--;if (node->count == 0) {gone = 1;__quicklistDelNode(quicklist, node);//当前节点的ziplist的entry个数为0,就删除该节点} else {quicklistNodeUpdateSz(node);    //更新该节点的ziplist的总长度大小}quicklist->count--;/* If we deleted the node, the original node is no longer valid */return gone ? 1 : 0;
}

6.查找元素

Redis中没有提供直接查找元素的API。查找元素是通过遍历查找的,这就需要通过上面所讲的迭代器quicklistIter

quicklistIter *iter = quicklistGetIterator(ql, AL_START_HEAD);
quicklistEntry entry;
int i = 0;
while (quicklistNext(iter, &entry)) {   //获取迭代器中的下一个元素//比较当前的压缩列表节点存储的元素与所要查找的是否相同if (quicklistCompare(entry.zi, (unsigned char *)"bar", 3)) {//进行一些操作...........}i++;
}

总结 quicklist的特性

  • 其是一个节点为ziplist的双端链表
  • 节点采用了ziplist,解决了传统链表的内存占用和易产生内存碎片问题
  • 对比单个ziplist,quicklist使用了多个ziplist,那每个ziplist的entry个数就可以控制的比较小,解决了连续空间申请效率和ziplist变更的时间复杂度过于大的问题
  • 中间节点可以压缩,进一步节省了内存

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

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

相关文章

前端学习之路-项目实战(1)

每日吐槽&#xff1a;有一个奇怪的问题&#xff0c;怎么一眼看出一个求职者是否是培训班出来的&#xff0c;有的求职上写着&#xff0c;希望大家坦诚一点&#xff0c;but&#xff0c;你这艘诚实的泰坦尼克号终究还是撞上了社会阴暗面的冰山&#xff0c;OMG&#xff0c;不让包装…

PDF文件内容可以转成word版本吗?答案是肯定的 PDF转word的方法

一&#xff0c;PDF转Word的必要性 随着信息技术的飞速发展&#xff0c;文档的格式和转换成为了我们日常生活和工作中不可避免的一部分。其中&#xff0c;PDF转Word的需求尤为突出。PDF作为一种跨平台的文档格式&#xff0c;具有阅读效果好、不易被篡改等优点&#xff0c;但在编…

【阿里淘天笔试题汇总】2024-04-10-阿里淘天春招笔试题-三语言题解(CPP/Python/Java)

&#x1f36d; 大家好这里是KK爱Coding &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新淘天近期的春秋招笔试题汇总&#xff5e; &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f…

Flutter学习11 - Future 与 FutureBuilder

1、Future 可以利用 Future 实现异步调用 1.1、Future 的两种形式 自定义一个结果类 class Response {String _data;Response(this._data); }自定义方法实现 Future Future<Response> testFuture() {var random Random();int randomNumber random.nextInt(10);if …

【STM32G431RBTx】备战蓝桥杯嵌入式→省赛试题→第十四届

文章目录 前言一、题目二、模块初始化三、代码实现interrupt.h:interrupt.c:main.h:main.c: 四、完成效果五、总结 前言 无 一、题目 二、模块初始化 1.LCD这里不用配置&#xff0c;直接使用提供的资源包就行 2.KEY, 四个按键IO口都要配置&#xff0c;分别是PB0, PB1,PB2,PA…

albef论文学习

首先要知道vit是啥东西。vit就是transformer模型在图像领域的运用。 transformer模型原本是用于自然语言的&#xff0c;encoder和decoder接受的都是文字。vit把图像分割成很多个小块&#xff0c;把各个小块拉长当成向量来用&#xff0c;接下来就是一样的。最后接一个全连接层做…

Qt | Qt 的重要文件简介(推荐)

一、项目文件(pro 文件)及其语法 1、项目文件(pro 文件)的作用是列举项目中的源文件, 2、pro 文件的语法形式为:“变量 操作符 值”,比如 QT += widgets,多个值之间使用空格分开。 3、pro 文件的注释:从“#”开始,直至本行结束。 4、pro 文件的操作符见下表 5、pro 文…

RAG应用开发实战(01)-RAG应用框架和解析器

1 开源解析和拆分文档 第三方的工具去对文件解析拆分&#xff0c;去将我们的文件内容给提取出来&#xff0c;并将我们的文档内容去拆分成一个小的chunk。常见的PDF word mark down, JSON、HTML。都可以有很好的一些模块去把这些文件去进行一个东西去提取。 优势 支持丰富的文…

Spring Boot 框架集成Knife4j

本次示例使用 Spring Boot 作为脚手架来快速集成 Knife4j,Spring Boot 版本2.3.5.RELEASE,Knife4j 版本2.0.7&#xff0c;完整代码可以去参考 knife4j-spring-boot-fast-demo pom.xml 完整文件代码如下 <?xml version"1.0" encoding"UTF-8"?> &l…

Meta宣布全新训推一体加速器:完全集成PyTorch 2,性能3倍提升

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了免费的人工智能中文站https://ai.weoknow.com 新建了收费的人工智能中文站https://ai.hzytsoft.cn/ 更多资源欢迎关注 Meta 疯狂砸入数十亿美元&#xff0c;一部分招揽人才&#xff0c;一部分造芯片。 Meta 正在不…

HCIE考试第三题:业务容器化及割接

文章目录 业务容器化及割接题目和做题步骤如下3.1业务容器化及割接3.1创建CCE集群solo3.2创建NAT网关3.2.1申请EIP3.2.2创建NAT网关3.2.3添加SNAT规则3.3创建节点池3.3.1 创建namespace3.3.2创建节点池3.4 安装命令行工具kubectl3.4.1上传kubectl3.4.2上传kubeconfig配置文件3.…

批归一化(BN)在神经网络中的作用与原理

文章目录 1. 批归一化&#xff08;BN&#xff09;在神经网络中的作用与原理1.1 作用与优势1.2 原理与推导 2. 将BN应用于神经网络的方法2.1 训练时的BN 2. 将BN应用于神经网络的方法2.1 训练时的BN2.2 测试时的BN代码示例&#xff08;Python&#xff09;&#xff1a; 3. BN的优…

做微信商城需要注意的几点问题

在社交平台做电商&#xff0c;已经非常常见&#xff0c;其中&#xff0c;最受欢迎的莫过于微信商城&#xff0c;基于微信公众号开发&#xff0c;进行宣传及变现&#xff0c;与用户建立长期双向联系&#xff0c;让用户形成长期稳定的购物习惯。看起来&#xff0c;这是一个很不错…

C++算法 —— 位运算

一、基本的位运算操作 1.基础位运算操作符 << : 二进制位整体左移 >> : 二进制位整体右移 ~ : 按位取反 & &#xff1a; 按位与 | &#xff1a; 按位或 ^ : 按位异或 &#xff08;无进位相加&#xff09; 2.给一个数n&#xff0c;确定它的二进制表示中第…

基于昇思的大地电磁智能反演模型达到业界SOTA,助力地球物理勘探加速智能化

近日&#xff0c;华为AI4S Lab与清华大学李懋坤教授团队、华为先进计算与存储实验室合作&#xff0c;基于昇腾AI处理器与昇思MindSpore AI框架打造了大地电磁智能反演模型。该模型通过变分自编码器&#xff08;VAE&#xff09;灵活嵌入了多物理先验知识&#xff0c;达到了业界S…

三次 Bspline(B样条曲线) NURBS曲线的绘制 matlab

先来了解几个概念&#xff1a; 1.1 节点向量&#xff1a; B-Spline需要定义曲线的节点向量U&#xff0c;它可以对应到Bezier曲线的参数u。 其元素个数 (m1) 和曲线阶数 k 、控制点个数n满足&#xff1a;m1k1n1 如果U的每段的距离是相等&#xff0c;那么这个B-Spline就被称为均…

多级菜单Mysql数据库表设计与创建

1.还是以Vue实现学院官网为例 文章地址&#xff1a;http://t.csdnimg.cn/jrJhE Vue 实现学院官网“菜单”当时是使用静态数据&#xff0c;也就是在页面上写死了的。 今天我们需要将“菜单”数据在数据库中进行维护&#xff0c;我们使用的是Mysql数据库 2.数据库的设计 我们的…

文心一言 VS 讯飞星火 VS chatgpt (234)-- 算法导论17.2 2题

二、用核算法重做练习17.1-3。练习17.1-3的内容是&#xff1a;假定我们对一个数据结构执行一个由 n 个操作组成的操作序列&#xff0c;当 i 严格为 2 的幂时第 i 个操作的代价为 i &#xff0c;否则代价为1。使用聚合分析确定每个操作的摊还代价。 文心一言&#xff1a; 练习…

Vue的学习之旅-part4

Vue的学习之旅-part1 vue的自带指令v-if v-else-if v-else虚拟DOM的复用v-show 与 v-if 的不同之处&#xff1a;v-if v-show各自合适的使用位置&#xff1a; v-for 循环v-for 循环遍历 :key"item" 绑定key&#xff0c;区分循环的内容循环的应用&#xff1a; 前几篇博…

基于SpringBoot+Vue的公园管理系统(源码+文档+部署+讲解)

一.系统概述 近年来&#xff0c;科技飞速发展&#xff0c;在经济全球化的背景之下&#xff0c;互联网技术将进一步提高社会综合发展的效率和速度&#xff0c;互联网技术也会涉及到各个领域&#xff0c;而公园管理系统在网络背景下有着无法忽视的作用。信息管理系统的开发是一个…