QML中常见模型使用

目录

  • 引言
  • 基础知识
  • 简单模型
  • 重复模型
    • 常见视图
      • ListView
      • Repeater
    • ListModel
    • QbjectModel
    • 容器模型
      • 数组
      • QStringList
      • QList<XX *>
    • QAbstractItemModel
    • QSortFilterProxyModel
  • 总结

引言

Qt Quick的基础组件中大量使用到模型,如ListView、Repeater等。视图所支持模型类型的也非常多,除了Qt提供的抽象模型QAbstractItemModel 、标准模型QStandardItemModel 之外,还支持队列QList、数组QJsonArray,甚至是数字。相对宽松的自由度也给后期代码维护带来困难,本文旨在对比QML中各种模型优劣(实现复杂度、可读性、可拓展性等),总结相对较好的实现方式。

基础知识

在这里插入图片描述
上图是Qt Widget中模型/视图的框架图,虽然Qt Quick中有些许差异,但基本继承原有框架,可进行对比参考。与MVC架构类似,但是又不相同,MVC包括模型、视图和控制。模型表示数据,视图表示用户界面,控制定义了用户的操作。它能够有效将数据和显示分离,提高了代码的灵活性。Qt的模型视图同样有这样的效果。但是Qt的模型视图将视图和控制放在一起,以便简化框架。另外,为了能够更好地处理用户输入,Qt的模型视图加入了代理(也称作委托)。通过代理能够自定义item的显示和编辑。

简单模型

在这里插入图片描述

模型是数据的集合,当然也可以用来表示单个数据,作为视图关键数据的抽象,但模型更新时视图被动更新,最简单的模型通过QObject的属性即可完成。例如,为实现上图中单张卡片信息展示,需要如下代码:
模型类:

class SimpleModel : public QObject
{Q_OBJECTQ_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged FINAL)Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL)Q_PROPERTY(int duration READ duration WRITE setDuration NOTIFY durationChanged FINAL)public:explicit SimpleModel(QObject *parent = nullptr);public:QString url() const;void setUrl(const QString &url);QString name() const;void setName(const QString &name);int duration() const;void setDuration(int duration);signals:void urlChanged();void nameChanged();void durationChanged();private:QString url_ = "qrc:/image/default_style.png";QString name_ = "VID_003.insv";int duration_ = 61;
};

卡片视图:

import QtQuick 2.15
import QtQuick.Layouts 1.15Rectangle {property var itemModelfunction secondConvertToTime(second) {let mininue = Math.floor(second / 60.0)let hour = Math.floor(mininue / 60.0)mininue %= 60second %= 60let dateTime = new Date(0, 0, 0, hour, mininue, second)let timeFormat = hour > 0 ? "hh:mm:ss" : "mm:ss"return Qt.formatTime(dateTime, timeFormat)}width: 290height: 180radius: 10color: '#FFD200'ColumnLayout {anchors.fill: parentanchors.margins: 10spacing: 6Image {Layout.fillWidth: trueLayout.fillHeight: truesource: itemModel.urlText {anchors.right: parent.rightanchors.bottom: parent.bottomanchors.margins: 6color: "white"font.family: "Microsoft YaHei UI"text: secondConvertToTime(itemModel.duration)}}Text {color: "white"font.family: "Microsoft YaHei UI"text: itemModel.name}}
}

注册上下文:

// 设置上下文属性
QQmlContext *context = engine.rootContext();
context->setContextProperty("SimpleModel", new SimpleModel);
视图组件使用:
import QtQuick 2.15
import QtQuick.Window 2.15Window {width: 640height: 480visible: truetitle: qsTr("Hello World")FootageCard {anchors.centerIn: parentitemModel: SimpleModel}
}

重复模型

在实际开发中,视图除了上述单张卡片之外,为了提高信息密度通常以列表的形式展示,可以是列表,可以是表格展示,也可以是流布局形式的栅格视图。而这种我们需要讨论的重复模型,则依赖于上述视图,因此需要想简单介绍Qt Quick中所提供的视图。

常见视图

[图片]

ListView与GridView、TableView类似,使用较为简单,Repeater则有一定差别,需要其他组件的配合或者是特殊处理。后面Model的例子中将主要配合ListView进行展示。

ListView

A ListView displays data from models created from built-in QML types like ListModel and XmlListModel, or custom model classes defined in C++ that inherit from QAbstractItemModel or QAbstractListModel.
A ListView has a model, which defines the data to be displayed, and a delegate, which defines how the data should be displayed. Items in a ListView are laid out horizontally or vertically. List views are inherently flickable because ListView inherits from Flickable.

相对可用视图组件,需要设置model和delegate才能正常使用,创建出来的组件能够自动布局,支持行布局、列布局,可通过orientation修改方向。由于父类是Flickable,继承有诸多鼠标操作,如滚动、拖拽等,但在子项中有MouseArea时,易出现冲突,需要通过MouseArea的pressedButtons属性去方式鼠标事件被抢夺。

ListView {width: 180height: 200model: ListModel {ListElement {name: "Bill Smith"number: "555 3264"}ListElement {name: "John Brown"number: "555 8426"}ListElement {name: "Sam Wise"number: "555 0473"}}delegate: Text {text: name + ": " + number}
}

Repeater

The Repeater type is used to create a large number of similar items. Like other view types, a Repeater has a model and a delegate: for each entry in the model, the delegate is instantiated in a context seeded with data from the model. A Repeater item is usually enclosed in a positioner type such as Row or Column to visually position the multiple delegate items created by the Repeater.

Repeater和ListView类似,是重复类的创建器,同样是需要设置model和delegate,但是并没有布局,也就是创建的组件x、y值并不会被修改,通常需要搭配布局组件使用,亦或者可以通过模型内的数据控制位置。

Row {anchors.fill: parentRepeater {model: 3Rectangle {width: 100; height: 40border.width: 1color: "yellow"}}
}

ListModel

直接使用Qt Quick中的已经封装好的元素ListModel,代码如下:

ListView {anchors.fill: parentspacing: 5model: ListModel {ListElement {name: "Bill Smith"url: "qrc:/image/default_style.png"duration: "61"}ListElement {name: "John Brown"url: "qrc:/image/default2_style.png"duration: "231"}ListElement {name: "Sam Wise"url: "qrc:/image/default3_style.png"duration: "15"}}delegate: FootageCard {itemModel: model}
}

上述代码中需要注意的是delegate内的model节点,和ListView中的model节点并不相同,delegate中表示的是单个item,而ListView中表示的整个数据集合。为了更加直观的表示,可增加点击修改模型信息的功能,如下所示:

    delegate: FootageCard {itemModel: modelMouseArea {id: mouseAreaanchors.fill: parent}Connections {target: mouseAreafunction onClicked() {model.name = "Jojo"}}}

[图片]

QbjectModel

比较特殊,可以省去delegate,代码如下:

ObjectModel {id: itemModelRectangle { height: 30; width: 80; color: "red" }Rectangle { height: 40; width: 80; color: "green" }Rectangle { height: 15; width: 80; color: "blue" }
}ListView {anchors.fill: parentmodel: itemModel
}

容器模型

数组

通过简单的数组或者是队列实现,一般使用在简单场景下。需要注意的是使用的时候简单数组,delegate能用到的item属性就不再是model而是modelData,如下所示:

ListView {anchors.fill: parentspacing: 5model: ["Bill Smith", "John Brown", "Sam Wise"]delegate: Text {text: modelData}
}

QStringList

也可以使用QStringList作为模型,如下所示(同样需要注册到上下文):

class CommonModel : public QObject
{Q_OBJECTQ_PROPERTY(QStringList stringList READ stringList WRITE setStringList NOTIFY stringListChanged FINAL)public:explicit CommonModel(QObject *parent = nullptr) : QObject{parent} {string_list_ << "Bill Smith" << "John Brown" << "Sam Wise";};public:QStringList stringList() const;void setStringList(const QStringList &string_list);signals:void stringListChanged();private:QStringList string_list_;
};
ListView {anchors.fill: parentspacing: 5model: CommonModel.stringListdelegate: Text {text: modelData}
}

QList<XX *>

使用上述代码在实际开发中存在问题,就是每个item只有一个属性,没法达到之前ListModel中的单个item有多个属性,可以采用QList<XX *>的方式(QVector相同),如下所示:

class CommonModel : public QObject
{Q_OBJECTQ_PROPERTY(QList<SimpleModel*> modelList READ modelList WRITE setModelList NOTIFY modelListChanged FINAL)public:explicit CommonModel(QObject *parent = nullptr) : QObject{parent} {qRegisterMetaType<SimpleModel *>("SimpleModel *");model_list_ << new SimpleModel(this) << new SimpleModel(this) << new SimpleModel(this);model_list_[0]->setName("Bill Smith");model_list_[1]->setName("John Brown");model_list_[2]->setName("Sam Wise");};public:QList<SimpleModel *> modelList() const;void setModelList(const QList<SimpleModel *> &model_list);signals:void modelListChanged();private:QList<SimpleModel *> model_list_;
};

上述代码使用的SimpleModel与前文相同,在使用时需要注册元对象类型,否则QML中将无法识别。注册完成后的使用方式与前文类似,只是需要将model改为modelData,如下所示:

ListView {anchors.fill: parentspacing: 5model: CommonModel.modelListdelegate: FootageCard {itemModel: modelData}
}

QAbstractItemModel

[图片]
[图片]

由于是抽象类重写纯虚函数以及部分虚函数,还需要准备容器用于记录数据,最后为了在QML中使用需要重写roleNames进行映射,代码如下:

class AbstractItemModel : public QAbstractItemModel
{Q_OBJECTpublic:explicit AbstractItemModel(QObject *parent = nullptr);enum ItemRole {ItemRoleUrl = Qt::UserRole,ItemRoleName,ItemRoleDuration,};public:// 重写纯虚函数:QModelIndex index(int row, int column,const QModelIndex &parent = QModelIndex()) const override;QModelIndex parent(const QModelIndex &index) const override;int rowCount(const QModelIndex &parent = QModelIndex()) const override;int columnCount(const QModelIndex &parent = QModelIndex()) const override;QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;public:// 修改属性bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;// QML属性映射QHash<int, QByteArray> roleNames() const override;private:QList<SimpleModel*> item_list_;QHash<int, QByteArray> role_names_;
};
AbstractItemModel::AbstractItemModel(QObject *parent): QAbstractItemModel(parent)
{role_names_.insert(ItemRoleUrl, "url");role_names_.insert(ItemRoleName, "name");role_names_.insert(ItemRoleDuration, "duration");item_list_ << new SimpleModel(this) << new SimpleModel(this) << new SimpleModel(this);item_list_[0]->setName("Bill Smith");item_list_[1]->setName("John Brown");item_list_[2]->setName("Sam Wise");
}QModelIndex AbstractItemModel::index(int row, int column, const QModelIndex &parent) const
{return hasIndex(row, column, parent) ? createIndex(row, column) : QModelIndex();
}QModelIndex AbstractItemModel::parent(const QModelIndex &index) const
{Q_UNUSED(index);return QModelIndex();
}int AbstractItemModel::rowCount(const QModelIndex &parent) const
{return parent.isValid() ? 0 : item_list_.count();
}int AbstractItemModel::columnCount(const QModelIndex &parent) const
{return parent.isValid() ? 0 : 1;
}QVariant AbstractItemModel::data(const QModelIndex &index, int role) const
{if (!index.isValid())return QVariant();if(index.row() >= rowCount()){return QVariant();}auto info = item_list_[index.row()];switch (role) {case ItemRoleUrl:return info->url();case ItemRoleName:return info->name();case ItemRoleDuration:return info->duration();}return QVariant();
}bool AbstractItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
{if (!index.isValid())return false;if(index.row() >= rowCount()){return false;}auto info = item_list_[index.row()];switch (role) {case ItemRoleUrl:info->setUrl(value.toString());break;case ItemRoleName:info->setName(value.toString());break;case ItemRoleDuration:info->setDuration(value.toInt());break;}emit dataChanged(index, index, QVector<int>{role});return true;
}QHash<int, QByteArray> AbstractItemModel::roleNames() const
{return role_names_;
}

QAbstractListModel与QAbstractItemModel类似,只是少了几个纯虚函数的实现,繁琐程度依旧。
看到这里大家应该会有疑惑,既然AbstractItemModel模型类内有一个容器item_list_,那为什么不直接用容器模型就可以了,还耗费时间增加一个模型类,凭空增加后续维护成本。
主要是为了增量更新,原本的容器模型是作为类的一个属性存在的,尽管item的属性变化是通过信号推送的,但item的增加和删除只能通过整个变更信号通知视图更新,也就是将原有的元素整体删除后再重建视图的全量更新。
当然因为是抽象类,还是需要实现部分代码才能有增加、删除功能,代码如下:

bool AbstractItemModel::insertRows(int row, int count, const QModelIndex &parent)
{if(!count){return false;}if(row >= rowCount()){row = rowCount();}else {if(!hasIndex(row, 0, parent)) {return false;}}beginInsertRows(QModelIndex(), row, row + count - 1);for(int i = 0; i < count; i++) {item_list_.insert(row, new SimpleModel(this));}endInsertRows();return true;
}bool AbstractItemModel::removeRows(int row, int count, const QModelIndex &parent)
{if(!count){return false;}if(!hasIndex(row, 0, parent)){return false;}beginRemoveRows(QModelIndex(), row, row + count - 1);for(int i = 0; i < count; i++) {item_list_.at(i)->deleteLater();item_list_.removeAt(i);}endRemoveRows();return true;
}

里面比较关键的就是beginInsertRows、endInsertRows和beginRemoveRows、endRemoveRows,也可以不重写insertRows、removeRows,只要保证前面的函数成组出现即可,里面会发送信号给视图进行处理。
QStandardItemModel
上述使用抽象模型的方式非常繁琐,过高的使用难度必然导致该方法的使用频率不高,甚至是在后期的维护中逐渐消亡。那有没有一种既使用简单,又能保证增量更新的方法。
可以直接使用QStandardItemModel,只需要将数据转换为标准元素QStandardItem。因为需要在QML中使用,再重写roleNames()即可。代码如下:

class StandardItemModel : public QStandardItemModel
{Q_OBJECTpublic:explicit StandardItemModel(QObject *parent = nullptr);enum ItemRole {ItemRoleUrl = Qt::UserRole,ItemRoleName,ItemRoleDuration,};public:// QML属性映射QHash<int, QByteArray> roleNames() const override;public:Q_INVOKABLE void append();Q_INVOKABLE void remove();private:QHash<int, QByteArray> role_names_;
};
StandardItemModel::StandardItemModel(QObject *parent): QStandardItemModel(parent)
{role_names_ = QStandardItemModel::roleNames();role_names_.insert(ItemRoleUrl, "url");role_names_.insert(ItemRoleName, "name");role_names_.insert(ItemRoleDuration, "duration");// 初始化auto item1 = new QStandardItem;item1->setData("qrc:/image/default_style.png", ItemRoleUrl);item1->setData("Bill Smith", ItemRoleName);item1->setData(61, ItemRoleDuration);auto item2 = new QStandardItem;item2->setData("qrc:/image/default2_style.png", ItemRoleUrl);item2->setData("John Brown", ItemRoleName);item2->setData(233, ItemRoleDuration);auto item3 = new QStandardItem;item3->setData("qrc:/image/default3_style.png", ItemRoleUrl);item3->setData("Sam Wise", ItemRoleName);item3->setData(15, ItemRoleDuration);appendRow(item1);appendRow(item2);appendRow(item3);
}QHash<int, QByteArray> StandardItemModel::roleNames() const
{return role_names_;
}void StandardItemModel::append()
{auto item = new QStandardItem;item->setData("qrc:/image/default_style.png", ItemRoleUrl);item->setData("Bill Smith", ItemRoleName);item->setData(61, ItemRoleDuration);appendRow(item);
}void StandardItemModel::remove()
{removeRow(rowCount() - 1);
}

QSortFilterProxyModel

代理模型在使用时需要源模型,也就是原始数据模型,针对原始模型进行代理,而QSortFilterProxyModel顾名思义是针对源模型的排序和过滤的代理。
在实际开发中,排序和过滤是对模型的高频操作,不同界面的模型内容可能只是因为排序方式、过滤内容的不同而被迫分为两个模型,例如界面A需要显示升序,而界面B需要显示降序,这里如果不对视图进行特殊操作就只能存在两种模型。
使用两种只有细微差别的模型显然不是明智的决定,这里则可以使用QSortFilterProxyModel完成过滤及排序,代理只是调整了两个数据集索引的映射关系,不是原Model的拷贝,尽管存在一部分维护映射关系的开销,但相比于维护两份相同的数据开销要小得多。同时将过滤和排序拆分出来,则属于非常合适的解耦,在数据格式类似的源模型之间还可以复用代理模型。
下面进行代理模型的实际应用展示,Demo整体分为三个ListView,第一列使用的是源模型,第二列是只有收藏的数据(卡片右侧的实心黄色圆形代表收藏,空心黄色圆圈代表未收藏),第三列使用的名称降序排序数据。
在这里插入图片描述

完整代码见QML常见模型使用源码。

总结

综上所述,对于简单模型可以采用QObject的属性系统,通过属性完成视图的被动刷新。对于重复模型建议使用QStandardItemModel,能够实现增量更新,后续也可以进行代理模型的拓展。元素数量极少且后续没有拓展需求才考虑使用容器模型。

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

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

相关文章

爬虫学习日记第八篇(爬取fofa某端口的协议排行及其机器数目,统计top200协议)

需求 找到最常用的200个协议 通过fofa搜索端口&#xff0c;得到协议排名前五名和对应机器的数目。 遍历端口&#xff0c;统计各个协议对应的机器数目&#xff08;不准&#xff0c;但能看出个大概&#xff09; 读写API API需要会员&#xff0c;一天只能访问1000次。 import…

SpringBoot常见异步编程,你会多少?

微信公众号访问地址&#xff1a;SpringBoot常见异步编程&#xff0c;你会多少&#xff1f; 近期热推文章&#xff1a; 1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表; 2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据; 3、基于Redis…

【C++面向对象】2.构造函数、析构函数

文章目录 【 1. 构造函数 】1.1 带参构造函数--传入数据1.2 无参构造函数--不传入数据1.3 实例1.4 拷贝构造函数 【 2. 析构函数 】 【 1. 构造函数 】 类的构造函数是类的一种特殊的成员函数&#xff0c;它会 在每次创建类的新对象时执行。 构造函数的名称与类的名称是完全相同…

2023年中国自动排气阀产业链、市场规模及存在问题分析]图[

自动排气阀是一种用于排除管道、容器或设备中累积的空气或气体的装置。在液体流动系统中&#xff0c;气体或空气可能会积聚在管道或容器中&#xff0c;影响流体流动、导致气锁和能效降低。自动排气阀的作用是在系统中的气体达到一定压力时&#xff0c;自动地释放气体&#xff0…

非关系型数据库-Redis

一、缓存概念 缓存是为了调节速度不一致的两个或多个不同的物质的速度&#xff0c;在中间对速度较慢的一方起到加速作用&#xff0c;比如CPU的一级、二级缓存是保存了CPU最近经常访问的数据&#xff0c;内存是保存CPU经常访问硬盘的数据&#xff0c;而且硬盘也有大小不一的缓存…

Java并发面试题:(五)volatile关键字

volatile 是什么 一旦一个共享变量&#xff08;类的成员变量、类的静态成员变量&#xff09;被volatile修饰之后&#xff0c;那么就具备了两层语义&#xff1a; 1&#xff09;保证了不同线程对这个变量进行操作时的可见性&#xff0c;即一个线程修改了某个变量的值&#xff0c…

python控制负数以16进制整型格式输出

实际使用时候&#xff0c;发现 python输出负数进程是 十进制和16进制一样的&#xff0c;就是16进制多了一个负号&#xff0c;和预期结果不同&#xff1b;比如我想要 -1输出 0xFFFFFFFF&#xff0c;可以参考如下方式&#xff1b; def TestPrintf(): ret -3print("test1 r…

Linux | gdb的基本使用

目录 前言 一、调试文件的生成 二、调试指令 1、选择调试文件 2、查看代码 3、运行代码 4、断点 5、打印与常显示 6、其他 总结 前言 前面我们学习了如何使用gcc/g来进行对代码进行编译&#xff0c;本章我们将使用gdb来对代码进行调试&#xff0c;学习本章的前提是有…

【专题】测试人员为什么需要学会做业务总结?

背景 如何回答以下这个问题的知识支撑&#xff1a;系统的测试重点在哪&#xff0c;难点是什么&#xff0c;怎么攻克&#xff0c;为什么要这样设计&#xff1f;项目交接效率&#xff1f; 同样是做业务测试&#xff0c;为什么有的人是A有的人只能C 二、框架 2.1 测试场景 重点…

RK3568笔记四:基于TensorFlow花卉图像分类部署

若该文为原创文章&#xff0c;转载请注明原文出处。 基于正点原子的ATK-DLRK3568部署测试。 花卉图像分类任务&#xff0c;使用使用 tf.keras.Sequential 模型&#xff0c;简单构建模型&#xff0c;然后转换成 RKNN 模型部署到ATK-DLRK3568板子上。 在 PC 使用 Windows 系统…

centos 内核对应列表 内核升级 linux

近期服务器频繁出现问题&#xff0c;找运维同事排查&#xff0c;说是系统版本和内核版本和官方不一致&#xff0c;如下&#xff1a; Release 用的是7.8, kernal 用的是 5.9 我一查确实如此&#xff1a; 内核&#xff1a; Linux a1messrv1 5.9.8-1.el7.elrepo.x86_64 发行版 Cen…

中文编程工具开发语言开发的实际案例:触摸屏点餐软件应用场景实例

中文编程工具开发语言开发的实际案例&#xff1a;触摸屏点餐软件应用场景实例 软件特色&#xff1a; 1、功能实用&#xff0c;操作简单&#xff0c;不会电脑也会操作&#xff0c;软件免安装&#xff0c;已内置数据库。软件在关闭的时候&#xff0c;可以设置会员数据备份到U盘&…

小谈设计模式(29)—访问者模式

小谈设计模式&#xff08;29&#xff09;—访问者模式 专栏介绍专栏地址专栏介绍 访问者模式角色分析访问者被访问者 优缺点分析优点将数据结构与算法分离增加新的操作很容易增加新的数据结构很困难4 缺点增加新的数据结构比较困难增加新的操作会导致访问者类的数量增加34 总结…

RHCE---搭建博客网站

一.实验要求&#xff1a; Server-NFS-DNS主机配置NFS服务器&#xff0c;将博客网站资源文件共享给Server-web主机&#xff0c;Server-NFS-DNS主机配置DNS Server-web主机配置web服务&#xff0c;通过域名www.openlab.com可以访问到自建的博客网站 二.准备工作 创建两台虚拟机…

10G SFP+线缆选购指南

凭借低成本和易安装的优势&#xff0c;在10G速率短距离传输中SFP线缆比SFP光模块更受欢迎。本文将从类型、优势、应用和选购指导等方面为您介绍10G SFP线缆&#xff0c;旨在帮助您更快做出购买决策。 10G SFP线缆&#xff1a;定义和类型 SFP线缆是一种高速线缆&#xff0c;两…

【LeetCode】 387. 字符串中的第一个唯一字符

题目链接 文章目录 所有方法 复杂度 ( O ( n ) O(n) O(n)、 O ( ∣ Σ ∣ ) O(|\Sigma|) O(∣Σ∣)) Python3方法一&#xff1a;collections.Counter() 统计频次方法二&#xff1a;哈希映射 { key字符&#xff1a;value【首次出现的索引 or -1 出现多次】}方法三&#xff1a; c…

Leetcode 1 两数之和 (暴力循环 HashMap* ) 含set、数组、map作哈希表的特性分析*

Leetcode 1 两数之和 &#xff08;暴力循环 哈希表&#xff09; 解法1 &#xff1a; 暴力循环解法2 : 哈希表HashMap法:red_circle:为什么想到用哈希表呢&#xff1f;:red_circle:为什么想到用map呢&#xff1f;:red_circle:归纳使用数组、set、map做哈希法&#xff1a; 题目链…

【2023淘宝双十一活动什么时间开始?天猫双十一2023具体时间安排

2023双十一活动什么时间开始&#xff1f;让我们先来了解一下双十一的优惠活动以及玩法吧。请收藏这份2023年淘宝天猫双十一玩法优惠攻略&#xff0c;让你轻松购得心仪的商品&#xff01; 红包派送 活动期间&#xff0c;每天都可以领取超级红包&#xff01;请注意&#xff0c…

Redis LFU缓存淘汰算法

前言 Redis 在 4.0 版本之前的缓存淘汰算法&#xff0c;只支持 random 和 lru。random 太简单粗暴了&#xff0c;可能把热点数据给淘汰掉&#xff0c;一般不会使用。lru 比 random 好一点&#xff0c;会优先淘汰最久没被访问的数据&#xff0c;但是它也有一个缺点&#xff0c;…