【Redis】List源码剖析

news2024/11/17 15:49:37

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

在这里插入图片描述

文章目录

  • List源码剖析
    • 回顾
    • List底层解析
      • 概述
      • ziplist 与 listpack
        • ziplist
          • ziplist 的结构
          • ziplist 节点结构
          • ziplist 的劣势
        • listpack
          • listpack 的结构
          • listpack 元素的结构
          • listpack 与 ziplist 结构对比
          • listpack 的优势
      • quicklist
        • 为什么选择 quicklist
        • quicklist 结构
        • quicklist 节点结构
        • quicklist 中的 listpack 到底能存储多少元素?
      • lpush是如何执行的?
        • 函数调用关系图
  • 总结
        • List 数据类型的发展历程:
        • ziplist
        • listpack
        • quicklist
        • LPUSH 命令的实现

List源码剖析


回顾


首先,我们先复习一下redisObject和Redis中的内部编码,这里只做简单回顾,详细介绍见《String源码剖析》。

redisObject:

在 Redis 中,redisObject 是用于表示 Redis 数据类型的通用结构。它封装了所有的数据对象,提供了对不同数据类型的一致接口。redisObject 结构体的设计使得 Redis 能够灵活、高效地管理内存和执行操作。下面是 redisObject 的结构:

image-20240529145712242

关于其中每个类项的具体介绍,见《String源码剖析》。


List底层解析


概述


在Redis的发展历程中,List数据类型的底层实现经历了几次重要的演变,从最初的双向链表到后来的快速列表。

Redis的早期版本(2.2及以前),List类型的数据结构采用的是双向链表(LinkedList)。这种实现方式的主要优点在于其插入和删除操作的时间复杂度为O(1),非常高效,特别适合频繁插入和删除的场景。然而,每个节点需要额外的指针存储空间,导致内存开销较大,并且对于大量数据的线性扫描性能较低。

随着Redis的发展,开发团队意识到需要一种更节省内存的解决方案。因此,在Redis 2.2到3.2版本中,引入了压缩列表(ZipList) 作为List数据类型的另一种底层实现。双向链表继续用于存储较大规模的List,而压缩列表则用于存储元素数量较少或每个元素字节数较小的List。 压缩列表的优点在于其内存紧凑,大大减少了内存开销,但其插入和删除操作的性能较差,因为需要移动大量数据,不适合频繁变更的List。

为了结合双向链表和压缩列表的优点,Redis 3.2版本引入了快速列表(QuickList)。QuickList是一种混合结构,每个节点是一个压缩列表,多个压缩列表通过双向链表连接在一起。这种设计结合了压缩列表的内存紧凑性和双向链表的高效插入、删除操作,在保持内存利用率的同时,提供了更好的操作性能。

在Redis 6.0及以后的版本中,QuickList继续作为List数据结构的主要实现方式。开发团队对QuickList的实现和细节进行了进一步的优化,增强了其性能和内存利用效率。

总结一下:

  • Redis 2.2及以前:主要使用双向链表(LinkedList)。
  • Redis 2.2至3.2:根据情况选择双向链表(LinkedList)或压缩列表(ZipList)。
  • Redis 3.2及以后:主要使用快速列表(QuickList),兼顾内存紧凑性和操作性能。

在详细介绍底层实现前,我们先从上面的redisObject中的typeencodingList结构中的取值看起。

注:示例源码如无特殊声明,都来自Redis7。

type

#define OBJ_LIST 1      /* List object. */

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_QUICKLIST 9 /* Encoded as linked list of listpacks */

#define OBJ_ENCODING_LISTPACK 11 /* Encoded as a listpack */

可以看出,在Redis7linkedlistziplist都已经不再使用,而是使用quicklistlistpack

由于linkedlist已经久久没有使用了,所以本篇文章将不会介绍它,但是ziplistRedis6中还依然在quicklist中使用,直到Redis7才更换为listpack,所以为了介绍listpackziplist的介绍是必不可少的。


ziplist 与 listpack


ziplistlistpack 详细解析见 《ziplist与listpack源码剖析》这篇文章。这里我对文章的主要内容做一总结,以便于大家理解 list 底层结构。

ziplist

ziplist 是一种以字节数组形式存在的紧凑数据结构,主要用于存储小型列表。它通过减少内存占用,优化存储空间,提高数据访问的效率。ziplist 适用于元素数量较少且不经常变动的场景。

ziplist 的结构

一个 ziplist 由以下几个部分组成:

  1. zlbytesziplist 的总字节数,4 个字节。
  2. zltail:到列表尾节点的偏移量,4 个字节。
  3. zllenziplist 中包含的节点数量,2 个字节。
  4. entry:实际存储的节点,节点数量根据 zllen 决定。
  5. zlend:特殊标记,标识 ziplist 的结束,1 个字节,值为 0xFF。
ziplist 节点结构

每个节点简单可以看作三部分组成(具体的实现见下面源码):

  1. prevlen:前一个节点的长度,用于快速向后遍历,1 或 5 个字节。
    • 如果前一个 entry 占用字节数小于 254,那么 prevlen 只用 1 个字节来表示就足够了。
    • 如果前一个 entry 占用字节数大于等于 254,那么 prevlen 就用 5 个字节来表示,其中第 1 个字节的值是 254(作为这种情况的标记),后面 4 个字节存储一个整型值来表示前一个 entry 的占用字节数。
  2. encoding:当前节点的编码方式,1 个字节。
  3. entry-data:实际存储的内容,根据编码方式不同,长度可变。

ziplist 结构图

ziplist 的劣势
  1. 连锁更新:由于 ziplist 中的元素存储了上一个元素的长度,当插入一个大于等于 254 字节的节点时,后续节点的 prevlen 需要扩展为 5 个字节,这会导致连锁更新,直到最后一个节点。
  2. 紧凑性不足:虽然 ziplist 使用紧凑的编码方式存储数据,但其实现相对复杂,编码和解码的过程需要更多的计算。
  3. 操作性能不高listpack 对插入和删除操作进行了优化,尤其是在处理大数据量的场景下,相比 ziplist 有显著的性能提升。
  4. 逻辑复杂,维护性不高ziplist 的实现复杂,编码和内存管理逻辑导致维护和调试难度较大。

listpack

在 Redis 7 之前,ziplist 是一种常用的数据结构,用于实现压缩列表和哈希表中的小型数据。然而,随着 Redis 的发展,ziplist 逐渐被 listpack 取代。listpack 是一种更新、更高效的数据存储结构,专为优化内存使用和提高性能而设计。

listpack 是一个紧凑的、连续的内存块,用于存储一组小的字符串或整数。它的设计目标是通过高效的内存布局和紧凑的编码格式,最大限度地减少内存占用。与 ziplist 相比,listpack 在存储密度和访问效率上都有显著提升。

listpack 的结构

一个 listpack 的基本结构如下:

  1. Total Bytes:表示整个 listpack 的总字节数(包括自身)。
  2. Number of Elements:表示 listpack 中元素的数量。
  3. Entry 1, Entry 2, … Entry N:存储的每个元素,可以是字符串或整数。
  4. End Byte:结束标志,固定为 0xFF。
listpack 元素的结构
  1. encoding-type :定义该元素的编码类型,会对不同长度的整数和字符串进行编码。
  2. element-data:实际存放的数据。
  3. element-tot-len:整个元素的长度,包含 encoding + data 的长度,用于反向遍历。

listpack 结构图

listpack 与 ziplist 结构对比

listpack 与 ziplist 结构对比图

listpack 的优势
  1. 无连锁更新:现在每个 listpack 元素都只存储自己的长度,不会发生像 ziplist 那样插入一个 254 字节及以上的元素引起的连锁更新。
  2. 内存效率listpack 通过紧凑的编码方式大幅减少了内存占用,特别是对小整数和短字符串的存储进行了优化。
  3. 操作性能:改进了内存布局,使得插入和删除操作更加高效,避免了大规模的数据移动,提升了操作性能。
  4. 兼容性和扩展性:作为新的数据结构,listpack 设计时考虑了更多的扩展性和兼容性问题,能够更好地适应 Redis 的未来发展需求。

通过对 ziplistlistpack 的理解,可以更好地了解 Redis 中 quicklist 的设计和性能改进。


quicklist


在 Redis 中,quicklist 是一种高效的数据结构,用于实现列表类型的数据存储。quicklist 结合了 listpack 和双向链表(linked list)的优势,旨在优化内存使用和操作性能。

为什么选择 quicklist

Redis 的列表类型数据在使用过程中会面临两种需求:

  1. 存储大量的小元素时,希望减少内存开销。
  2. 对列表进行频繁的插入、删除和访问操作时,希望保证操作的高效性。

传统的 ziplist 虽然在内存使用上表现不错,但在处理大量数据和频繁操作时性能较差。而双向链表则在频繁操作上表现良好,但内存开销较大。为了解决这些问题,Redis 引入了 quicklist

quicklist 结构

quicklist 是一种结合了 listpack 和双向链表的混合结构。它将多个 listpack 节点组成一个双向链表,每个链表节点存储一个 listpack

Quicklist
+-------+    +-------+    +-------+
| Node1 |<-> | Node2 |<-> | Node3 |
+-------+    +-------+    +-------+
    |             |             |
    v             v             v
+---------+   +---------+   +---------+
| Listpack|   | Listpack|   | Listpack|
+---------+   +---------+   +---------+

quicklist 包含以下字段:

  • head:指向第一个 quicklistNode 的指针。
  • tail:指向最后一个 quicklistNode 的指针。
  • count:所有 listpack 中所有条目的总计数。
  • lenquicklistNode 的数量。
  • fill:各个节点的填充因子。
  • compress:两端不压缩的节点深度,0 表示不压缩。
  • bookmark_count:书签的数量。
  • bookmarks[]:书签数组,用于快速访问特定位置。

diagram (3)

源码如下:

typedef struct quicklist {
    quicklistNode *head;
    quicklistNode *tail;
    unsigned long count;        /* total count of all entries in all listpacks */
    unsigned long len;          /* number of quicklistNodes */
    signed 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;
quicklist 节点结构

每个 quicklist 节点存储一个 listpack,并包含以下字段:

  • prev:指向前一个节点的指针。
  • next:指向下一个节点的指针。
  • entry:指向 listpack 数据的指针。
  • sz:当前 listpack 的字节大小。
  • count:当前 listpack 中的元素个数。
  • encoding:编码方式。
  • container:容器类型。
  • recompress:用于重新压缩的标志。
  • attempted_compress:重试压缩的次数。
  • extra:额外的标志位。

diagram (4)

源码如下:

typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *entry;
    size_t sz;             /* entry size in bytes */
    unsigned int count : 16;     /* count of items in listpack */
    unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
    unsigned int container : 2;  /* PLAIN==1 or PACKED==2 */
    unsigned int recompress : 1; /* was this node previous compressed? */
    unsigned int attempted_compress : 1; /* node can't compress; too small */
    unsigned int dont_compress : 1; /* prevent compression of entry that will be used later */
    unsigned int extra : 9; /* more bits to steal for future usage */
} quicklistNode;
quicklist 中的 listpack 到底能存储多少元素?

每个quicklistNode中都有一个listpack,但是这个listpack到底能存储多少个数据呢?太多会影响插入删除效率,太少又无法压缩多少空间。所以,这个中间值很难决定,要结合实际的应用场景来决定。

使用者可以在客户端或者Redis服务端的配置文件中使用list-max-ziplist-size这个参数来决定quicklistNode中的listpack到底可以存储多少数据。

我们可以在Redis客户端查询一下相关的参数:

127.0.0.1:6379> config get list*
1) "list-max-listpack-size"
2) "-2"
3) "list-compress-depth"
4) "0"
5) "list-max-ziplist-size"
6) "-2"
  1. list-max-listpack-size

这个参数定义了 listpackquicklist 中的最大大小。取值可以是负值或者正值:

  • 负值:表示每个 listpack 的最大字节数。取值范围通常是 -1-9,其中:
    • -1 表示 listpack 的最大字节数为 2 1 2^1 21 = 2 KB。
    • -2 表示 listpack 的最大字节数为 2 2 2^2 22 = 4 KB。
    • -3 表示 listpack 的最大字节数为 2 3 2^3 23 = 8 KB。
    • 以此类推,-9 表示 listpack 的最大字节数为 2 9 2^9 29 = 512 KB。
  • 正值:表示每个 listpack 中允许的最大元素个数。具体值可以根据应用场景和需求设置,例如 1 表示每个 listpack 中最多存储 1 个元素,10 表示最多存储 10 个元素。
  1. list-compress-depth

这个参数定义了 quicklist 两端不压缩的节点深度。取值为非负整数:

  • 0:表示禁用压缩,即不压缩任何节点。
  • 正整数:表示 quicklist 的两端有多少个节点不会被压缩。例如:
    • 1 表示 quicklist 的头部和尾部各保留一个未压缩的节点,其他节点可以被压缩。
    • 2 表示头部和尾部各保留两个未压缩的节点,其他节点可以被压缩。
    • 以此类推,具体值可以根据性能和内存需求进行调整。
  1. list-max-ziplist-size

这是一个旧参数,已经被list-max-listpack-size取代,与list-max-listpack-size用法相同。

list-max-listpack-size这个参数在Redis内部实现中就是quicklist中的fill类项,这两个项的取值和意义都是相同的。


lpush是如何执行的?


首先,先来看源码中lpush对应的函数:

  1. lpushCommand:这个函数是 Redis 中 LPUSH 命令的具体实现。它调用 pushGenericCommand 函数,并传递参数以表示应将元素推入列表的头部。
  2. pushGenericCommand:这是一个更通用的函数,用于实现多个类似的命令(LPUSH、RPUSH、LPUSHX、RPUSHX)。它接受参数来确定将元素推入列表的头部(LIST_HEAD)还是尾部(LIST_TAIL),以及是否仅在键存在时执行推送操作(xx 参数)。
    1. 查找键:使用 lookupKeyWrite 在数据库中查找键 c->argv[1]
    2. 类型检查:使用 checkType 检查键是否存在以及是否为列表类型。如果类型不匹配,则返回。
    3. 处理不存在的键
      • 如果键不存在且 xx 标志被设置(表示仅在键存在时推送),则发送零回复给客户端并返回。
      • 如果键不存在且 xx 未设置,则创建一个新的列表对象,并使用 dbAdd 将其添加到数据库中。
    4. 推送元素
      • 调用 listTypeTryConversionAppend 尝试转换列表的内部表示。
      • 使用 listTypePush 将从 c->argv[2] 开始的元素逐个推入列表的头部或尾部(根据 where 参数决定)。
      • 每次推送元素后,增加服务器的脏计数器 server.dirty
/* LPUSH <key> <element> [<element> ...] */
void lpushCommand(client *c) {
    pushGenericCommand(c,LIST_HEAD,0);
}

/* Implements LPUSH/RPUSH/LPUSHX/RPUSHX. 
 * 'xx': push if key exists. */
void pushGenericCommand(client *c, int where, int xx) {
    int j;

    robj *lobj = lookupKeyWrite(c->db, c->argv[1]);
    if (checkType(c,lobj,OBJ_LIST)) return;
    if (!lobj) {
        if (xx) {
            addReply(c, shared.czero);
            return;
        }

        lobj = createListListpackObject();
        dbAdd(c->db,c->argv[1],lobj);
    }

    listTypeTryConversionAppend(lobj,c->argv,2,c->argc-1,NULL,NULL);
    for (j = 2; j < c->argc; j++) {
        listTypePush(lobj,c->argv[j],where);
        server.dirty++;
    }

    addReplyLongLong(c, listTypeLength(lobj));

    char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
    signalModifiedKey(c,c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
}

其次,我们来看看createListListpackObject这个创建listpack的函数:

createListListpackObject 函数用于创建一个新的列表对象,并将其初始化为一个空的 listpack。

robj *createListListpackObject(void) {
    unsigned char *lp = lpNew(0);
    robj *o = createObject(OBJ_LIST,lp);
    o->encoding = OBJ_ENCODING_LISTPACK;
    return o;
}

可以看到,Redis7中不直接创建一个quicklist,而是先创建一个listpack,当超过一个listpack的最大长度后才将其转换为quicklist,这与Redis6中直接创建quicklist的做法不同。

再者,从listTypeTryConversionAppend这个函数开始向下继续挖,可以看到listpack是如何进行内部类型的转换的:

当列表以 listpack 编码存储并且即将添加的元素可能导致其超过配置的大小限制时,系统会自动将列表从 listpack 转换为 quicklist 编码。这种转换确保了列表在大小增长时的高效存储和操作,从而优化了 Redis 的性能和内存使用。函数通过计算要添加的元素的总字节数和长度,并在必要时执行转换操作,以实现这一目的。

/* This is just a wrapper for listTypeTryConversionRaw() that is
 * able to try conversion before adding elements to the list. */
void listTypeTryConversionAppend(robj *o, robj **argv, int start, int end,
                                 beforeConvertCB fn, void *data)
{
    listTypeTryConversionRaw(o, LIST_CONV_GROWING, argv, start, end, fn, data);
}

/* Check if the list needs to be converted to appropriate encoding due to
 * growing, shrinking or other cases. */
static void listTypeTryConversionRaw(robj *o, list_conv_type lct,
                                     robj **argv, int start, int end,
                                     beforeConvertCB fn, void *data)
{
    if (o->encoding == OBJ_ENCODING_QUICKLIST) {
        if (lct == LIST_CONV_GROWING) return; /* Growing has nothing to do with quicklist */
        listTypeTryConvertQuicklist(o, lct == LIST_CONV_SHRINKING, fn, data);
    } else if (o->encoding == OBJ_ENCODING_LISTPACK) {
        if (lct == LIST_CONV_SHRINKING) return; /* Shrinking has nothing to do with listpack */
        listTypeTryConvertListpack(o, argv, start, end, fn, data);
    } else {
        serverPanic("Unknown list encoding");
    }
}

/* Check the length and size of a number of objects that will be added to list to see
 * if we need to convert a listpack to a quicklist. Note that we only check string
 * encoded objects as their string length can be queried in constant time.
 *
 * If callback is given the function is called in order for caller to do some work
 * before the list conversion. */
static void listTypeTryConvertListpack(robj *o, robj **argv, int start, int end,
                                       beforeConvertCB fn, void *data)
{
    serverAssert(o->encoding == OBJ_ENCODING_LISTPACK);

    size_t add_bytes = 0;
    size_t add_length = 0;

    if (argv) {
        for (int i = start; i <= end; i++) {
            if (!sdsEncodedObject(argv[i]))
                continue;
            add_bytes += sdslen(argv[i]->ptr);
        }
        add_length = end - start + 1;
    }

    if (quicklistNodeExceedsLimit(server.list_max_listpack_size,
            lpBytes(o->ptr) + add_bytes, lpLength(o->ptr) + add_length))
    {
        /* Invoke callback before conversion. */
        if (fn) fn(data);

        quicklist *ql = quicklistCreate();
        quicklistSetOptions(ql, server.list_max_listpack_size, server.list_compress_depth);

        /* Append listpack to quicklist if it's not empty, otherwise release it. */
        if (lpLength(o->ptr))
            quicklistAppendListpack(ql, o->ptr);
        else
            lpFree(o->ptr);
        o->ptr = ql;
        o->encoding = OBJ_ENCODING_QUICKLIST;
    }
}

最后,通过listTypePush这个函数,看看lpush是如何添加元素到list的:

listTypePush 函数的功能是将一个元素插入指定的列表对象 subject 中,位置由 where 参数指定,可以是头部(LIST_HEAD)或尾部(LIST_TAIL)。函数会根据列表对象的编码类型(quicklistlistpack)以及元素的编码类型(整数或字符串)来决定如何进行插入操作。

对于 quicklist 编码的列表,函数将整数值转换为字符串后插入;对于 listpack 编码的列表,函数直接插入整数值或字符串值。

image-20240530141219880

/* The function pushes an element to the specified list object 'subject',
 * at head or tail position as specified by 'where'.
 *
 * There is no need for the caller to increment the refcount of 'value' as
 * the function takes care of it if needed. */
void listTypePush(robj *subject, robj *value, int where) {
    if (subject->encoding == OBJ_ENCODING_QUICKLIST) {
        int pos = (where == LIST_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL;
        if (value->encoding == OBJ_ENCODING_INT) {
            char buf[32];
            ll2string(buf, 32, (long)value->ptr);
            quicklistPush(subject->ptr, buf, strlen(buf), pos);
        } else {
            quicklistPush(subject->ptr, value->ptr, sdslen(value->ptr), pos);
        }
    } else if (subject->encoding == OBJ_ENCODING_LISTPACK) {
        if (value->encoding == OBJ_ENCODING_INT) {
            subject->ptr = (where == LIST_HEAD) ?
                lpPrependInteger(subject->ptr, (long)value->ptr) :
                lpAppendInteger(subject->ptr, (long)value->ptr);
        } else {
            subject->ptr = (where == LIST_HEAD) ?
                lpPrepend(subject->ptr, value->ptr, sdslen(value->ptr)) :
                lpAppend(subject->ptr, value->ptr, sdslen(value->ptr));
        }
    } else {
        serverPanic("Unknown list encoding");
    }
}
函数调用关系图

diagram (3)


总结


List 数据类型的发展历程:
  • Redis 2.2 及以前:使用双向链表(linkedlist)。
  • Redis 2.2 到 3.2:根据情况使用双向链表或压缩列表(ziplist)。
  • Redis 3.2 及以后:引入快速列表(quicklist),结合了压缩列表和双向链表的优点。
  • Redis 7:Listpack 取代了 Ziplist,成为新的数据存储结构。
ziplist
  • ziplist 是一种紧凑的字节数组,用于存储小型列表,减少内存占用。
  • ziplist 结构包括总字节数、尾节点偏移量、节点数量、实际存储的节点和结束标记。
  • ziplist 的劣势包括连锁更新、紧凑性不足、操作性能不高和维护复杂性。
listpack
  • listpack 是一种更新的、更高效的数据存储结构,设计目标是通过高效的内存布局和紧凑的编码格式,最大限度地减少内存占用。
  • listpack 结构包括总字节数、元素数量、实际存储的元素和结束标记。
  • listpack 相比 Ziplist 具有无连锁更新、内存效率高、操作性能好、兼容性和扩展性强的优势。
quicklist
  • quicklist 结合了 Listpack 和双向链表的优点,旨在优化内存使用和操作性能。
  • quicklist 结构由多个 Listpack 组成的双向链表,每个节点存储一个 listpack。
  • quickList 提供了高效的内存利用和快速的插入、删除操作。
LPUSH 命令的实现
  • 详细解析了 LPUSH 命令的实现过程,包括创建新的 Listpack 对象、转换内部类型和推入元素等步骤。

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

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

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

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

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

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

相关文章

使用第三方工具percona-xtrabackup进行数据备份与恢复

目录 准备工作 开始安装 innobackupex的使用 完全备份 增量备份 数据恢复 本次需要用到的软件 mysql 5.7.35percona-xtrabackup-24-2.4.8 ps&#xff1a;---MySQL必须是5.7的版本&#xff0c;在8.0之后已经不支持 percona-xtrabackup-24 系统版本CentOS7.9 准备工作 …

面试题 17.05. 字母与数字(前缀和)

给定一个放有字母和数字的数组&#xff0c;找到最长的子数组&#xff0c;且包含的字母和数字的个数相同。 返回该子数组&#xff0c;若存在多个最长子数组&#xff0c;返回左端点下标值最小的子数组。若不存在这样的数组&#xff0c;返回一个空数组。 示例 1: 输入: ["…

SpringBoot+layui实现Excel导入操作

excel导入步骤 第三方插件引入插件 效果图 &#xff08;方法1&#xff09;代码实现&#xff08;方法1&#xff09;Html代码&#xff08; 公共&#xff09;下载导入模板 js实现 &#xff08;方法1&#xff09;上传文件实现 效果图&#xff08;方法2&#xff09;代码实现&#xf…

一碗米线火了24年,蒙自源六一再献新作

当一碗热气腾腾的米线在餐桌上飘香四溢&#xff0c;你是否会想起那个陪伴了无数食客24年的名字——蒙自源&#xff1f;在这个充满欢笑与童真的六一儿童节&#xff0c;蒙自源米线品牌再度发力&#xff0c;用全新的儿童餐系列为孩子们带来了一份特别的节日礼物。 蒙自源&#xf…

性价比为王,物流商怎么选择高效的国际物流管理平台

在全球化贸易日益繁荣的今天&#xff0c;国际物流行业作为链接国内商家和海外市场的重要桥梁&#xff0c;发挥着极其重要的作用。 然而&#xff0c;随着国际物流市场竞争的加剧&#xff0c;对物流商来说&#xff0c;也面临着成本管控和效率提升的双重挑战。今天我们会重点探讨…

AI之下 360让PC商业生态大象起舞

时隔7年&#xff0c;淘宝PC版在前不久迎来重磅升级&#xff0c;在产品体验、商品供给、内容供给等方面做了全面优化&#xff0c;以全面提升PC端的用户体验&#xff1b;当大家都以为移动互联网时代下APP将成为主流时&#xff0c;PC端却又成为了香饽饽。其实PC端被重视&#xff0…

【Qt】【模型视图架构】代理模型示例

文章目录 1. 基本排序/过滤模型Basic Sort/Filter Model Example2. 自定义排序/过滤模型Custom Sort/Filter Model ExampleFilterLineEdit类定义及实现MySortFilterProxyModel类定义及实现 1. 基本排序/过滤模型Basic Sort/Filter Model Example 官方提供的基本排序/过滤模型示…

LeetCode42:接雨水

题目描述 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 代码 单调栈 class Solution { public:int trap(vector<int>& height) {stack<int> stk;int result 0;stk.push(0);for (int …

Android设备获取OAID调研和实现

什么是OAID、AAID、VAID OAID OAID是"Android ID"&#xff08;安卓ID&#xff09;的一种替代方案&#xff0c;其全称为"Open Anonymous Identifier"&#xff08;开放匿名标识符&#xff09;。 因传统的移动终端设备标识如国际移动设备识别码&#xff08;…

【Python内功心法】:深挖内置函数,释放语言潜能

文章目录 &#x1f680;一、常见内置函数&#x1f308;二、高级内置函数⭐1. enumerate函数&#x1f44a;2. eval函数❤️3. exec函数&#x1f4a5;4. eval与exec 中 globals与locals如何用☔4-1 globals 参数&#x1f3ac;4-2 locals 参数 ❤️5. filter函数&#x1f44a;6. z…

发电机组故障的原因、解决方案及解决措施

发电机组故障的原因、解决方案及解决措施可以总结如下&#xff1a; 一、故障原因 供电中断 原因&#xff1a;电网故障、线路短路或电力负荷过重等。 燃油问题 原因&#xff1a;燃油供应系统问题&#xff0c;如燃油管路堵塞、燃油质量不佳等。 轴承过热 原因&#xff1a;轴承过…

小学生四则运算练习器,用户可以选择进行加减乘除任意一项,也可以选择退出,然后每次计算后会提示正确与否,最后计算总分然后并给出评语。

⑴ 用户可以从菜单中选择某种运算进行练习。具体包括&#xff1a;加法&#xff0c;减法&#xff0c;乘法&#xff0c;除法&#xff1b;也可以退出程序. ⑵ 用户可以指定每次练习的题目数量、设置练习的总分&#xff1b; ⑶ 每小题练习后给出结果正确与否的提示&#xff1b;一…

【机器学习】智能选择的艺术:决策树在机器学习中的深度剖析

在机器学习的分类和回归问题中&#xff0c;决策树是一种广泛使用的算法。决策树模型因其直观性、易于理解和实现&#xff0c;以及处理分类和数值特征的能力而备受欢迎。本文将解释决策树算法的概念、原理、应用、优化方法以及未来的发展方向。 &#x1f680;时空传送门 &#x…

基于单片机的微型嵌入式温度测量仪的设计与实现分析

摘要 &#xff1a; 作为信息技术中重要的技术手段之一嵌入式单片机系统已经被应用到越来越多不同的行业领域中。如&#xff0c;各种手持监测设备、智能家电设备等。当前展开对单片机的微型嵌入式温度测量仪的设计和实现研究&#xff0c;从微型嵌入式单片机相关理论入手&#xf…

Java Apache Jaccard文本相似度匹配初体验

文章目录 前言一、文本相似度算法的选择二、常见的文本相似度算法介绍三、使用示例1、引入jar包2、方法示例3、Jaccard源码剖析4、Jaccard源码解释 写在最后 前言 产品今天提了个需求&#xff0c;大概是这样的&#xff0c;来&#xff0c;请看大屏幕。。。额。。。搞错了&#…

IDEA插件开发:自动生成setter

背景 在给Java局部变量的实体赋值时&#xff0c;往往有很多setter&#xff0c;一个一个写很麻烦&#xff0c;也会漏掉&#xff0c;因此开发一款插件&#xff0c;可以自动生成局部变量实体的所有setter。 插件效果如下&#xff1a; 可以在plugin marketplace 搜索&#xff1…

Nginx 1.26.0 爆 HTTP/3 QUIC 漏洞,建议升级更新到 1.27.0

据悉&#xff0c;Nginx 1.25.0-1.26.0 主线版本中涉及四个与 NGINX HTTP/3 QUIC 模块相关的中级数据面 CVE 漏洞&#xff0c;其中三个为 DoS 攻击类型风险&#xff0c;一个为随机信息泄漏风险&#xff0c;影响皆为允许未经身份认证的用户通过构造请求实施攻击。目前已经紧急发布…

【ARM-Linux篇】u-boot编译

一、u-boot简介 uboot是一种通用的引导加载程序&#xff0c;它可以用于多种嵌入式系统&#xff0c;支持多种操作系统&#xff0c;如Linux, Android,NetBSD等。uboot的主要作用是将操作系统内核从存储设备&#xff08;如Flash, SD卡等&#xff09;加载到内存中&#xff0c;并执…

Thread的stop和interrupt的区别

Thread.stop Thread.stop()方法已被废弃。 因为本质上它是不安全的&#xff0c;使用该方法可能会导致数据、资源不一致的问题&#xff0c; public class ThreadDemo {static class MyThread extends Thread {Overridepublic void run() {while (true) {try {Thread.sleep(10…

C#WPF数字大屏项目实战03--数据内容区域

1、内容区域划分 第一行标题&#xff0c;放了几个文本框 第二行数据&#xff0c;划分成3列布局 2、第1列布局使用UniformGrid控件 最外面放UniformGrid&#xff0c;然后里面放3个GroupBox控件&#xff0c;这3个groupbox都是垂直排列 3、GroupBox控件模板 页面上的3个Group…