文章目录

  • 前言
  • redis中的set结构
    • 疑问1 :为什么使用数组后 整体时间复杂度还是O(1)
    • 疑问2: set特性是无序的那为什么当元素少的时候 用连续数组 去存储呢?
    • 疑问3:当元素少于512的时候即使用intset存储的时候 是如何维护唯一性的?
    • 疑问4: set底层的hashtable 如何处理hash冲突的 对比redis 的Hash类型的处理策略
    • 实际看看 intset 和hashtable 的底层存储数据区别
  • 使用例子
    • 在线用户统计(去重)
    • 相似度计算(用户标签交集)
    • 秒杀活动参与者去重
  • 总结

前言

我们已经分析了三种不同数据类型 在redis中的使用 以及底层的数据结构

Redis常用数据结构以及多并发场景下的使用分析:String类型
Redis常用数据结构以及多并发场景下的使用分析:Hash类型
Redis常用数据结构以及多并发场景下的使用分析:List类型

接下来 我们要来分析下 set的基本使用

redis中的set结构

按照惯例首先还是需要搞清楚 redis中的 set 结构是怎样去处理的

Redis 中 Set 类型的两种底层实现方式:intset(整数集合) 和 hashtable(哈希表)
查找、插入、删除时间复杂度为O(1)

intset(整数集合)

所有元素都是整数(可以转为 long 类型);元素个数小于 set-max-intset-entries(默认 512);

特点:

连续内存:底层是一个连续的数组(有序数组 优势利用上了 内存的局部性 所以是连续内存 )
查找:使用二分查找(因为数组是有序的)
插入:时保持有序(需要移动数组)

hashtable(哈希表)

元素中存在非整数(如字符串、UUID、中文等);或者元素数量超过 set-max-intset-entries(默认512);或者手动添加一个大字符串,也会触发升级。

特点:

哈希表结构:底层是一个 dict;插入查找效率 O(1):基于哈希函数定位;空间占用较大:因为要维护哈希桶和冲突处理结构;支持任意类型元素(比如 "abc", "hello", "uuid");

看完基本介绍 不知道你是否有以下的疑问?

疑问1 :为什么使用数组后 整体时间复杂度还是O(1)

前面说了intset是一个内存连续 并且内部integer 连续的数组 采用二分查找 我们都只带二分查找时间复杂度是O(logn)而不是O(1) 为什么还是O(1)呢?

解答:

那就是因为 集合较小(只有512个元素),在实践中表现为“常数时间”—— 所以平均复杂度可认为是 O(1)。

疑问2: set特性是无序的那为什么当元素少的时候 用连续数组 去存储呢?

解答:

高效性 + 空间紧凑性。

内存连续性 为了 利用上 CPU Cache 缓存的机制 尽管查找用的是 二分查找 O(log n),但数据小、内存局部性好,速度非常快

疑问3:当元素少于512的时候即使用intset存储的时候 是如何维护唯一性的?

解答:

先二分查找,发现已经存在元素就不插入

疑问4: set底层的hashtable 如何处理hash冲突的 对比redis 的Hash类型的处理策略

Set 类型:

Set 的底层是 dict,插入元素之前 Redis 会先判断:

当前 key 是否已经存在于某个 hash 桶的链表中(即元素是否存在)

插入逻辑如下:

if (dictFind(set, value) == NULL) {dictAdd(set, value); // 插入
}

如果哈希冲突,但值不相等(链表中找不到值),会继续插入;

如果值已经存在(即链表中有相同元素),则不插入,保持唯一性。

结论:

Set 类型允许哈希冲突,只要值不同就能插入;不允许值重复,即使哈希值一样,Redis 也会用 memcmp 判断值是否相等

Hash 类型(field-value 映射):
Hash 类型本质是:

dict<field, value>

与 Set 类似:

冲突时使用链式哈希解决

同一个 field 不允许重复,如果 field 已存在,则更新 value

插入逻辑:

dictReplace(hash, field, value);  // 如果存在就覆盖

总结:

Set:
bucket[5] --> ["apple"] --> ["banana"]      // 值不能重复,但可以 hash 冲突Hash:
bucket[5] --> ["name" => "Tom"] --> ["age" => 20] // key 冲突则更新 value

实际看看 intset 和hashtable 的底层存储数据区别

设计一个实验去看插入519个数据之前 和 插入520个数据的区别

@Service
@RequiredArgsConstructor
@Slf4j
public class SetEncodingDemoService {private final RedisTemplate<String, String> redisTemplate;// key for testingprivate static final String SET_KEY = "demo:set:encoding";// 清空 & 初始化测试public void runEncodingTest() {redisTemplate.delete(SET_KEY);log.info("开始添加整数元素,触发 intset -> hashtable 升级演示");// 1. 添加 500 个整数(仍为 intset 编码)IntStream.range(0, 500).forEach(i -> {redisTemplate.opsForSet().add(SET_KEY, String.valueOf(i));});log.info("添加 500 个整数完成,请用命令 `debug object demo:set:encoding` 查看编码,预计为 intset");// 2. 继续添加超过限制,触发升级IntStream.range(500, 520).forEach(i -> {redisTemplate.opsForSet().add(SET_KEY, String.valueOf(i));});log.info( 添加第 520 个整数完成,预计编码变为 hashtable");}
}

测试类

@SpringBootTest
class SetEncodingDemoServiceTest {@Autowiredprivate SetEncodingDemoService setEncodingDemoService;@Testpublic void testEncodingChange() {setEncodingDemoService.runEncodingTest();}
}

分别执行步骤 1 和步骤 2:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

可以非常详细的看到 内部数据结构由intset 变成了hashtable

使用例子

okok 我们已经了解了 set底层为整数数组+hashtable 那么接下来我们就来看看实际的使用例子
wait 稍等一下 set 有哪些常见的使用场景

特异元素都多少个? 求交并集?元素是否存在?

ok 那不就是可以使用这些场景吗

在线用户统计(去重)

记录所有在线用户,使用 Set 去重,并可实时获取当前在线用户数量。

redis结构:

// userID加入
SADD online:users userId
// 求在key(online:users)下有多少个userID
SCARD online:users
// 移除online:users userId 元素
SREM online:users userId
@Service
@RequiredArgsConstructor
public class OnlineUserService {private final RedisTemplate<String, Object> redisTemplate;private static final String ONLINE_USER_KEY = "online:users";// 用户上线public void userOnline(String userId) {redisTemplate.opsForSet().add(ONLINE_USER_KEY, userId);}// 用户下线public void userOffline(String userId) {redisTemplate.opsForSet().remove(ONLINE_USER_KEY, userId);}// 获取当前在线人数public long getOnlineCount() {return redisTemplate.opsForSet().size(ONLINE_USER_KEY);}// 获取所有在线用户public Set<Object> getAllOnlineUsers() {return redisTemplate.opsForSet().members(ONLINE_USER_KEY);}
}

相似度计算(用户标签交集)

为每个用户打标签,利用标签集合之间的交集计算相似度(如推荐系统)。

redis结构

// 给user:tags:uid123  打上三个标签
SADD user:tags:uid123 "java" "redis" "ai"
// 给user:tags:uid456  打上三个标签
SADD user:tags:uid456 "java" "kafka" "ai"
// 求交集
SINTER user:tags:uid123 user:tags:uid456
@Service
@RequiredArgsConstructor
public class UserTagSimilarityService {private final RedisTemplate<String, Object> redisTemplate;// 为用户添加标签public void addTags(String userId, String... tags) {String key = "user:tags:" + userId;redisTemplate.opsForSet().add(key, (Object[]) tags);}// 获取两个用户的共同标签(交集)public Set<Object> getCommonTags(String uid1, String uid2) {String key1 = "user:tags:" + uid1;String key2 = "user:tags:" + uid2;return redisTemplate.opsForSet().intersect(key1, key2);}// 相似度得分(交集大小 / 并集大小)public double computeSimilarity(String uid1, String uid2) {String k1 = "user:tags:" + uid1;String k2 = "user:tags:" + uid2;Set<Object> inter = redisTemplate.opsForSet().intersect(k1, k2);Set<Object> union = redisTemplate.opsForSet().union(k1, k2);if (union == null || union.isEmpty()) return 0.0;return (double) inter.size() / union.size();}
}

秒杀活动参与者去重

高并发下秒杀活动记录用户参与,每个用户只能参与一次。

Redis 结构:

// seckill:activity:10001 下 增加一个 userId
SADD seckill:activity:10001 userId
@Service
@RequiredArgsConstructor
public class SeckillService {private final RedisTemplate<String, Object> redisTemplate;// 秒杀参与:返回是否成功(是否重复)public boolean tryJoinSeckill(String activityId, String userId) {String key = "seckill:activity:" + activityId;Long added = redisTemplate.opsForSet().add(key, userId);return added != null && added > 0;}// 获取参与人数public long getJoinCount(String activityId) {return redisTemplate.opsForSet().size("seckill:activity:" + activityId);}// 判断用户是否参与过public boolean hasJoined(String activityId, String userId) {return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember("seckill:activity:" + activityId, userId));}
}

总结:

场景Redis Key 样例操作高并发优势
在线用户统计online:usersSADD / SCARD去重 + 实时查询人数
用户相似度分析user:tags:uid123SINTER / SUNION集合交集操作原子且高效
秒杀用户去重seckill:activity:10001SADD去重保证每人只能参与一次

总结

Set类型的并发优势:

原子性:所有Set操作都是原子的
去重特性:自动处理重复数据
高性能:O(1)时间复杂度的添加/删除/查询
集合运算:支持交集、并集、差集等复杂操作
内存高效:紧凑的数据结构

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

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

相关文章

Linux中rw-rw-r--相关的访问权限讲解

下面就是关于 rw-rw-r-- 的知识图谱式讲解。核心节点&#xff1a;rw-rw-r-- (文件权限表示法) 这是一个在 Linux/Unix 操作系统中&#xff0c;通过 ls -l 命令查看到的&#xff0c;用于描述文件或目录访问权限的10字符字符串。分支一&#xff1a;字符串的解剖 (Anatomy of the …

C#异常处理:更优雅的方式

C#异常处理&#xff1a;更优雅的方式 在 C# 编程的世界里&#xff0c;异常处理是绕不开的重要环节。程序运行时难免会出现各种意外&#xff0c;若处理不当&#xff0c;可能导致程序崩溃&#xff0c;给用户带来糟糕体验。所以&#xff0c;掌握更优雅的异常处理方式&#xff0c;对…

Qt6中模态与非模态对话框区别

一.阻塞 vs 非阻塞1.模态对话框阻塞父窗口&#xff1a;打开后&#xff0c;用户必须先处理该对话框&#xff08;关闭或完成操作&#xff09;&#xff0c;才能继续操作父窗口。应用场景&#xff1a;强制用户立即响应的场景&#xff0c;如确认对话框、登录窗口、文件选择器等。2.非…

处理Web请求路径参数

目录 1. 路径变量&#xff08;Path Variable&#xff09; 2. 查询参数&#xff08;Query Parameter&#xff09; 3. 表单参数&#xff08;Form Data&#xff09; 4. 请求体JSON参数&#xff08;Request Body JSON&#xff09; 5. 请求头参数&#xff08;Header Parameters&…

创客匠人:技术赋能下的创始人 IP 打造与内容创作新逻辑

在知识变现的浪潮中&#xff0c;创始人 IP 的核心竞争力始终围绕内容展开&#xff0c;但内容创作的效率与质量往往成为瓶颈。创客匠人基于对行业的深刻洞察&#xff0c;探索出技术与内容融合的路径&#xff0c;为创始人 IP 打造提供了新的思路 —— 不再将内容创作视为单纯的输…

Mysql分片:一致性哈希算法

一、一致性哈希的核心原理哈希取模最大的痛点是&#xff1a;当分片数量&#xff08;例如数据库节点数&#xff09;发生变化时&#xff0c;几乎所有数据的哈希结果都会改变&#xff0c;导致大规模的数据迁移。一致性哈希就是为了解决这个“伸缩性差”的问题而诞生的。核心思想&a…

前端学习 vben 之 axios interceptors

前端学习 vben 之 axios interceptors interceptor 拦截器&#xff0c;是一种软件设计模式&#xff0c;核心思想就是在程序执行的特定阶段&#xff08;如请求发送前&#xff0c;响应返回后&#xff0c;方法调用前后等&#xff09;自动插入自定义逻辑。实现对核心流程的“拦截”…

【java面试day4】redis缓存-数据持久化

文章目录问题&#x1f4ac; Question 1相关知识问题 &#x1f4ac; Question 1 Q&#xff1a;redis作为缓存&#xff0c;数据的持久化是怎么做的? A&#xff1a;有两种机制&#xff0c;一种是RDB&#xff0c;RDB会在指定的时间间隔内将内存中的数据生成快照&#xff0c;保存…

Vue3中element plus默认获取最近一周和上个月的时间区间并在后端分开传值

<el-form-item label"结算时间&#xff1a;" prop"datetimerangevalue"><el-date-pickerv-model"datetimerangevalue"value-format"YYYY-MM-DD HH:mm:ss"type"datetimerange"range-separator"至"start-p…

SQLAlchemy数据库连接密码特殊字符处理完全指南

引言 在使用SQLAlchemy连接数据库时&#xff0c;我们通常使用URL格式指定连接信息&#xff0c;如mysqlpymysql://user:passwordhost:port/database。然而&#xff0c;当密码中包含特殊字符&#xff08;如、#、$、!等&#xff09;时&#xff0c;会导致URL解析错误&#xff0c;进…

1.4 ARM安全参考架构(PSA Certified)

目录1.4.1 PSA Certified概述1.4.2 PSA认证级别详解1.4.3 PSA与TF-A的关系1.4.4 PSA安全模型实现信任根(RoT)架构关键安全服务&#xff1a;1.4.5 认证流程实践1.4.6 典型应用案例参考资料1.4.1 PSA Certified概述 ARM Platform Security Architecture (PSA) Certified 是一套完…

企业网络安全的“金字塔”策略:构建全方位防护体系的核心思路

在数字化转型的浪潮中&#xff0c;企业的网络安全已从单一的防护措施&#xff0c;发展成为多层次、全方位的安全体系。如何精准应对日益复杂的网络威胁&#xff0c;成为众多企业关注的焦点。本文将分享企业构建高效安全防护“金字塔”的核心思路。一、从“排查隐患”到“主动防…

爬虫-request模块使用

1.使用和安装2.代码测试打印返回的内容&#xff0c;默认是请求体中的标识.text 是打印源代码设置一下编码

HTML + CSS + JavaScript

目录 1 HTML HTML 文件基本结构 HTML 开发工具 HTML 常见标签 标题标签&#xff1a;h1 - h6 段落标签&#xff1a;p 换行标签&#xff1a;br 图片标签&#xff1a;img 超链接标签&#xff1a;a 表格标签 表单标签 form 标签 input 标签 select 标签 textarea 标…

Java 与 MySQL 性能优化:MySQL连接池参数优化与性能提升

文章目录引言一、连接池的基本概念与作用二、关键连接参数详解2.1 max_connections2.2 wait_timeout2.3 interactive_timeout2.4 connect_timeout2.5 thread_cache_size三、连接池参数不合理导致的性能问题3.1 连接耗尽3.2 响应变慢3.3 连接失效3.4 资源浪费四、连接池参数优化…

浪潮CD1000-移动云电脑-RK3528芯片-2+32G-开启ADB ROOT破解教程

浪潮CD1000-移动云电脑-RK3528芯片-232G-安卓9-开启ADB ROOT破解教程破解教程&#xff1a;1.先下载好开心电视助手&#xff08;下载地址及其他版本&#xff1a;【工具大全】-【开心电视助手3.8&#xff0f;4.0&#xff0f;4.6&#xff0f;6.0&#xff0f;6.2&#xff0f;6.3&am…

【网络编程】简易的 p2p 模型,实现两台虚拟机之间的简单点对点通信,并以小见大观察 TCP 协议的具体运行

文章目录基本概念业务拆解代码实现准备工作实现被动的功能——多线程指针函数实现主动的功能——用户选择界面主函数代码执行效果意外收获总结推荐一个零声教育学习教程&#xff0c;个人觉得老师讲得不错&#xff0c;分享给大家&#xff1a;[Linux&#xff0c;Nginx&#xff0c…

react状态管理库 - zustand

什么是zustand&#xff1f; zustand 是一个轻量级、快速且可扩展的 React 状态管理库&#xff0c;旨在提供一种简单直接的方式来管理应用状态&#xff0c;而无需其他解决方案通常伴随的繁琐代码。根据官方 Zustand 文档&#xff0c;Zustand 是“一个使用简化 flux 原理的小型、…

粗排样本架构升级:融合LTR特征提升模型性能的技术实践

粗排样本架构升级&#xff1a;融合LTR特征提升模型性能的技术实践 ——基于PySpark的样本构建与特征工程深度解析 一、粗排系统的定位与技术演进 在推荐系统级联架构中&#xff0c;​粗排&#xff08;Rough Ranking&#xff09;​​ 承担着关键过渡角色&#xff1a;从召回层获…

CCF-GESP 等级考试 2025年6月认证C++四级真题解析

1 单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09;第1题 在C中&#xff0c;声明一个指向整型变量的指针的正确语法是&#xff08; &#xff09;。A. int* ptr; B. *int ptr; C. int ptr*; D. ptr …