redis基本数据结构-set

news2024/12/24 21:56:08

文章目录

    • 1. set的基本介绍
      • 1.1. set底层结构之hash表的简单介绍
      • 1.2. 常用命令
    • 2. 常见的业务场景
      • 2.1. 标签系统
      • 2.2. 社交网络好友关系

1. set的基本介绍

参考链接:https://mp.weixin.qq.com/s/srkd73bS2n3mjIADLVg72A
redis 的 set 数据结构是一个无序的集合,可以存储不重复的元素。适合于多种应用场景,尤其是在需要去重、快速查找和集合运算的场合。从上面的介绍可以看到set主要的特性为:
无序性:元素在set中没有特定的顺序;
唯一性:set中的元素是唯一的,不能重复。
其中还有一个比较重要的特性就是高效性。因为redis 的 Set 数据结构是基于哈希表(hash table)实现的,这也是为什么对元素的添加、删除和查找操作的时间复杂度都是 O(1) 的原因。

1.1. set底层结构之hash表的简单介绍

  1. 哈希函数:哈希表使用哈希函数将元素映射到一个数组的索引中。通过这个索引,我们可以快速定位到存储在该位置的元素。
  2. 存储结构:在 Redis 中,Set 存储元素时会使用一个哈希表,其键是元素的值,值通常是一个指向该元素的指针。这样,每个元素的插入、查找或删除操作都可以直接通过哈希表的索引进行。
  3. 添加元素(SADD):当添加一个新元素时,Redis 计算该元素的哈希值,并根据这个哈希值找到相应的索引位置。如果该位置为空(即没有相同的元素),就将该元素插入数组中。这一过程的时间复杂度是 O(1)。
  4. 删除元素(SREM):删除一个元素时,Redis 同样计算该元素的哈希值并找到相应的索引位置。如果该元素存在,就将其从哈希表中移除。这一过程也是 O(1)。
  5. 查找元素(SISMEMBER):查找一个元素是否在集合中,Redis 计算该元素的哈希值,并通过该哈希值定位到对应的索引位置。如果该元素存在于该位置,则返回 true;如果不存在,则返回 false。这一过程同样是 O(1)。
    理论上的限制
    尽管哈希表的平均时间复杂度是 O(1),在某些情况下,哈希表可能会出现冲突(即不同的元素计算出相同的哈希值)。当发生冲突时,多个元素可能会存储在同一个索引位置,导致查找、插入和删除的性能下降。此时,复杂度可能退化为 O(n),其中 n 是发生冲突的元素数量。然而,Redis 使用了一些策略(如动态扩容和链表/树结构来处理冲突)来保持哈希表的性能,使得在大多数情况下,操作的时间复杂度仍然可以保持在 O(1)。

1.2. 常用命令

SADD key member [member ...]:将一个或多个成员添加到集合中。
SREM key member [member ...]:移除集合中的一个或多个成员。
SISMEMBER key member:判断某个成员是否是集合中的成员。
SMEMBERS key:返回集合中的所有成员。
SCARD key:返回集合中成员的数量。
交集:SINTER key1 [key2 ...]:返回所有给定集合的交集。
并集:SUNION key1 [key2 ...]:返回所有给定集合的并集。
差集:SDIFF key1 [key2 ...]:返回第一个集合与其他集合的差集。
SADD myset "apple" "banana" "orange"
SREM myset "banana"
SISMEMBER myset "apple"  # 返回 1 (true)
SMEMBERS myset  # 返回 ["apple", "orange"]
SCARD myset  # 返回 2

SADD set1 "a" "b" "c" 
SADD set2 "b" "c" "d" 
SINTER set1 set2  # 返回 ["b", "c"] 
SUNION set1 set2  # 返回 ["a", "b", "c", "d"] 
SDIFF set1 set2   # 返回 ["a"]

2. 常见的业务场景

一般业务场景的使用是会根据对应数据结构的特性来说明的:

  1. 去重:当需要存储一组唯一的用户 ID、标签或其他信息时,可以使用 Set 来自动去重。例如,存储用户的浏览历史,确保每个用户的历史记录中没有重复的页面。【唯一性】
  2. 社交网络:在社交网络应用中,可以使用 Set 来表示用户的好友关系。比如,用户 A 的好友可以存储在 Set 中,快速判断用户 B 是否是用户 A 的好友。【唯一性,高效性】
  3. 标签系统:对于文章、商品等,可以使用 Set 来存储相关标签,方便进行标签查询和管理。【高效性】
  4. 投票系统:可以使用 Set 存储已经投票的用户 ID,确保每个用户只能投一次票。【唯一性】
  5. 实时分析:在实时分析场景中,可以使用 Set 来跟踪活跃用户、访问过的页面等。【高效性】

2.1. 标签系统

标签系统:Set类型可用于存储和处理具有标签特性的数据,如商品标签、文章分类标签等。
背景
在一个内容平台上,用户可以给文章打上不同的标签,系统需要根据标签过滤和推荐文章。
在这里插入图片描述
优势

  1. 快速查找:使用Set可以快速判断一个元素是否属于某个集合。
  2. 灵活的标签管理:方便地添加和删除标签,实现标签的灵活管理。
  3. 集合运算:通过集合运算,如交集和并集,可以轻松实现复杂的标签过滤逻辑。

代码实现

package main

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v8"
)

var ctx = context.Background()

// Redis 客户端初始化
var rdb = redis.NewClient(&redis.Options{
    Addr:     "",  // Redis 服务器地址
    Password: "", // 密码
    DB:       0,                  // 使用默认 DB
})

// 文章标签管理结构体
type ArticleTagManager struct{}

// 给文章添加标签
func (atm *ArticleTagManager) AddTagToArticle(articleID string, tags []string) error {
    _, err := rdb.SAdd(ctx, "article:"+articleID+":tags", tags).Result()
    if err != nil {
       return err
    }

    for _, tag := range tags {
       _, err := rdb.SAdd(ctx, "global:tags:"+tag, "article:"+articleID).Result()
       if err != nil {
          return err
       }
    }
    log.Printf("Added tags %v to article %s\n", tags, articleID)
    return nil
}

// 从文章中删除标签
func (atm *ArticleTagManager) RemoveTagFromArticle(articleID string, tag string) error {
    _, err := rdb.SRem(ctx, "article:"+articleID+":tags", tag).Result()
    if err != nil {
       return err
    }
    log.Printf("Removed tag %s from article %s\n", tag, articleID)
    return nil
}

// 获取文章的所有标签
func (atm *ArticleTagManager) GetTagsOfArticle(articleID string) ([]string, error) {
    tags, err := rdb.SMembers(ctx, "article:"+articleID+":tags").Result()
    if err != nil {
       return nil, err
    }
    return tags, nil
}

// 根据标签获取文章列表
func (atm *ArticleTagManager) GetArticlesByTag(tag string) ([]string, error) {
    articles, err := rdb.SMembers(ctx, "global:tags:"+tag).Result()
    if err != nil {
       return nil, err
    }
    return articles, nil
}

// 获取文章的标签数量
func (atm *ArticleTagManager) GetTagCountOfArticle(articleID string) (int64, error) {
    count, err := rdb.SCard(ctx, "article:"+articleID+":tags").Result()
    if err != nil {
       return 0, err
    }
    return count, nil
}

// 获取某个标签的文章数量
func (atm *ArticleTagManager) GetArticleCountByTag(tag string) (int64, error) {
    count, err := rdb.SCard(ctx, "global:tags:"+tag).Result()
    if err != nil {
       return 0, err
    }
    return count, nil
}

func main() {
    atm := &ArticleTagManager{}

    // 为文章添加标签
    if err := atm.AddTagToArticle("1", []string{"Golang", "Redis"}); err != nil {
       log.Fatalf("Error adding tags: %v", err)
    }
    if err := atm.AddTagToArticle("2", []string{"Python", "Redis"}); err != nil {
       log.Fatalf("Error adding tags: %v", err)
    }
    if err := atm.AddTagToArticle("3", []string{"Golang", "Python"}); err != nil {
       log.Fatalf("Error adding tags: %v", err)
    }

    // 获取文章的所有标签
    if tags, err := atm.GetTagsOfArticle("1"); err == nil {
       fmt.Println("Tags for article 1:")
       for _, tag := range tags {
          fmt.Println(tag)
       }
    }

    // 根据标签获取文章列表
    if articles, err := atm.GetArticlesByTag("Golang"); err == nil {
       fmt.Println("Articles with tag 'Golang':")
       for _, article := range articles {
          fmt.Println(article)
       }
    }

    // 获取文章标签数量
    if count, err := atm.GetTagCountOfArticle("1"); err == nil {
       fmt.Printf("Article 1 has %d tags.\n", count)
    }

    // 获取某个标签的文章数量
    if count, err := atm.GetArticleCountByTag("Redis"); err == nil {
       fmt.Printf("Tag 'Redis' has %d articles associated.\n", count)
    }

    // 测试完移除整个集合
    rdb.Del(ctx, "article:1:tags")
    rdb.Del(ctx, "article:2:tags")
    rdb.Del(ctx, "article:3:tags")
    rdb.Del(ctx, "global:tags:Golang")
    rdb.Del(ctx, "global:tags:Redis")
    rdb.Del(ctx, "global:tags:Python")
}

运行结果:
在这里插入图片描述

2.2. 社交网络好友关系

社交网络好友关系:Set类型可以表示用户的好友列表,支持快速好友关系测试和好友推荐。
背景
在一个社交网络应用中,用户可以添加和删除好友,系统需要管理用户的好友关系。
在这里插入图片描述
优势

  1. 唯一性:保证好友列表中不会有重复的好友。
  2. 快速关系测试:快速判断两个用户是否互为好友。
  3. 好友推荐:利用集合运算,如差集,推荐可能认识的好友。

解决方案
使用Redis Set类型存储用户的好友集合,实现好友关系的管理。
代码实现

package main

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v8"
    "log"
)

var ctx = context.Background()

// Redis 客户端初始化
var rdb = redis.NewClient(&redis.Options{
    Addr:     "",  // Redis 服务器地址
    Password: "", // 密码
    DB:       0,                  // 使用默认 DB
})

// 添加好友
func addFriend(userOneID string, userTwoID string) error {
    _, err := rdb.SAdd(ctx, "user:"+userOneID+":friends", userTwoID).Result()
    if err != nil {
       return err
    }
    _, err = rdb.SAdd(ctx, "user:"+userTwoID+":friends", userOneID).Result()
    return err
}

// 判断是否是好友
func isFriend(userOneID string, userTwoID string) bool {
    return rdb.SIsMember(ctx, "user:"+userOneID+":friends", userTwoID).Val()
}

// 获取用户的好友列表
func getFriendsOfUser(userID string) ([]string, error) {
    friends, err := rdb.SMembers(ctx, "user:"+userID+":friends").Result()
    return friends, err
}

// 删除好友
func removeFriend(userOneID string, userTwoID string) error {
    _, err := rdb.SRem(ctx, "user:"+userOneID+":friends", userTwoID).Result()
    if err != nil {
       return err
    }
    _, err = rdb.SRem(ctx, "user:"+userTwoID+":friends", userOneID).Result()
    return err
}

// 推荐可能认识的好友
func recommendFriends(userID string) ([]string, error) {
    // 获取用户的好友
    friends, err := getFriendsOfUser(userID)
    if err != nil {
       return nil, err
    }

    // 如果没有好友,则没有推荐
    if len(friends) == 0 {
       return nil, nil
    }

    // 找到所有好友的好友,以及去掉自己的好友和自己
    potentialFriends := make(map[string]struct{})

    for _, friendID := range friends {
       friendFriends, err := getFriendsOfUser(friendID)
       if err != nil {
          return nil, err
       }
       for _, potentialFriend := range friendFriends {
          // 排除自己和直接好友
          if potentialFriend != userID {
             potentialFriends[potentialFriend] = struct{}{}
          }
       }
    }

    // 将推荐的好友转换为切片
    recommendations := []string{}
    for friend := range potentialFriends {
       // 确保不是已经存在的好友
       if !isFriend(userID, friend) {
          recommendations = append(recommendations, friend)
       }
    }

    return recommendations, nil
}

func main() {
    // 示例:添加好友
    if err := addFriend("user1", "user2"); err != nil {
       log.Fatalf("Error adding friend: %v", err)
    }

    // 示例:检查好友关系
    if isFriend("user1", "user2") {
       fmt.Println("user1 and user2 are friends.")
    }

    // 示例:获取好友列表
    friends, err := getFriendsOfUser("user1")
    if err != nil {
       log.Fatalf("Error getting friends: %v", err)
    }
    fmt.Println("Friends of user1:", friends)

    // 示例:推荐可能认识的好友
    recommendations, err := recommendFriends("user1")
    if err != nil {
       log.Fatalf("Error recommending friends: %v", err)
    }
    fmt.Println("Recommended friends for user1:", recommendations)

    // 示例:删除好友
    if err := removeFriend("user1", "user2"); err != nil {
       log.Fatalf("Error removing friend: %v", err)
    }
}

注意事项:

  • 虽然Set是无序的,但Redis会保持元素的插入顺序,直到集合被重新排序。
  • Set中的元素是唯一的,任何尝试添加重复元素的操作都会无效。
  • 使用集合运算时,需要注意结果集的大小,因为它可能会影响性能。

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

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

相关文章

【JavaScript】数据结构之字典 哈希表

字典 键值对存储的,类似于js的对象,但在js对象中键[key]都是字符串类型或者会转换成字符串类型,因此后声明的键值会覆盖之前声明的值。字典以map表示,map的键不会转换类型。 let map new Map() map.set(a, 1) map.set(b, 2) ma…

智能门锁为何选择ESP32-S3芯片?低功耗高性能方案,启明云端乐鑫代理商

在科技日新月异的今天,家庭安全不再仅仅依赖于传统的锁和钥匙。智能门锁,作为智能家居系统的前沿产品,正逐渐走进千家万户,成为家庭安全的高科技守护者。 智能门锁是一种利用现代科技手段,通过电子化、信息化技术改进…

Linux.之设备树DTS(device tree source)(一)

一、概述 Device Tree是一种描述硬件的数据结构,相比于旧架构它起源于 OpenFirmware (OF),在过去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的冗余代码,相当多数的代码只是在描述板级硬件细节,而这些不同的…

【二十】【QT开发应用】listwidget右键菜单和删除item

创建项目,添加资源文件 在项目文件夹中创建resources资源文件夹。 在vs中打开qrc文件,选择添加资源文件。 选择我们resources资源文件中的所有文件作为资源文件。 最后不要忘记点击保存。 向ListWidget控件添加item 右键菜单 在.h文件中添加QMenu头…

代码随想录算法训练营第五十九天 | Bellman_ford 算法精讲

目录 Bellman_ford 算法精讲 思路 什么叫做松弛 模拟过程 方法一: Bellman_ford算法 Bellman_ford 算法精讲 题目链接:卡码网:94. 城市间货物运输 I 文章讲解:代码随想录 某国为促进城市间经济交流,决定对货物运…

maya-vray渲染蒙版

要用一个叫vrayMulWrapper的材质球,把alpha Conterbution调到-1,勾选matte surface启用蒙版物体。

【C++题解】1406. 石头剪刀布?

欢迎关注本专栏《C从零基础到信奥赛入门级(CSP-J)》 问题:1406. 石头剪刀布? 类型:二维数组 题目描述: 石头剪刀布是常见的猜拳游戏。石头胜剪刀,剪刀胜布,布胜石头。如果两个人出…

数据库索引底层数据结构之B+树MySQL中的页索引分类【纯理论干货,面试必备】

目录 1、索引简介 1.1 什么是索引 1.2 使用索引的原因 2、索引中数据结构的设计 —— B树 2.1 哈希 2.2 二叉搜索树 2.3 B树 2.4 最终选择之——B树 2.4.1 B树与B树的对比(面向索引)【面试题】 3、MySQL中的页 3.1 页的使用原因 3.2 页的结构 3.2.1 页文件头和页文件…

Unity实战案例全解析:PVZ 植物卡片状态分析

Siki学院2023的PVZ免费了,学一下也坏 卡片状态 卡片可以有三种状态: 1.阳光足够,(且cd好了可以种植) 2.阳光不够,(cd?好了:没好 (三目运算符)&…

Linux | 探索 Linux 信号机制:信号的产生和自定义捕捉

信号是 Linux 操作系统中非常重要的进程控制机制,用来异步通知进程发生某种事件。理解信号的产生、阻塞、递达、捕捉等概念,可以帮助开发者更好地编写健壮的应用程序,避免由于未处理的信号导致程序异常退出。本文将带你从基础概念开始&#x…

基于SpringBoot+Vue的牙科就诊管理系统(带1w+文档)

基于SpringBootVue的牙科就诊管理系统(带1w文档) 基于SpringBootVue的牙科就诊管理系统(带1w文档) 伴随着互联网发展,现今信息类型愈来愈多,信息量也非常大,那也是信息时代的缩影。近些年,电子元器件信息科学合理发展的趋势变的越…

【React】React18.2.0核心源码解读

前言 本文使用 React18.2.0 的源码,如果想回退到某一版本执行git checkout tags/v18.2.0即可。如果打开源码发现js文件报ts类型错误请看本人另一篇文章:VsCode查看React源码全是类型报错如何解决。 阅读源码的过程: 下载源码 观察 package…

C# System.BadImageFormatException问题及解决

C# System.BadImageFormatException问题 出现System.BadImageFormatException 异常有两种情况:程序目标平台不一致&引用dll文件的系统平台不一致。 异常参考 BadImageFormatException 程序目标平台不一致: 项目>属性>生成:x86 …

学LabVIEW编程,看编程书有些看不懂怎么办?

自学LabVIEW编程时,如果发现编程书籍内容难以理解,可以尝试以下几种方式来提高学习效果: 1. 从基础入手,逐步深入: LabVIEW是一种基于图形化编程的工具,不同于传统的编程语言,因此从基础概念开…

linux 操作系统下cupsenable命令介绍和使用案例

linux 操作系统下cupsenable命令介绍和使用案例 cupsenable 命令是 Linux 操作系统中用于启用 CUPS(通用打印服务)打印机的命令。它允许用户将指定的打印机重新启用,从而使其可以接受新的打印作业 cupsenable 命令概述 基本语法 bash cup…

LEAN 赋型唯一性(Unique Typing)之 n-provability 注解

《LEAN 赋型唯一性(Unique Typing)之 证明过程简介》 中,梳理了赋型唯一性(Unique Typing)牵涉的概念及相关推论与证明,此篇文章就先介绍 n-provability 的概念,记 ⊢ₙ 。其围绕的是赋型规则&a…

PHP创意无限一键生成小程序名片生成系统源码

创意无限,一键生成 —— 小程序名片生成系统,开启你的个性化商务新时代! 一、告别千篇一律,拥抱个性化名片 你还在使用那些千篇一律的传统纸质名片吗?是时候做出改变了!现在有了“创意无限一键生成小程序名…

Cisco Catalyst 9000 Series Switches, IOS XE Release 17.15.1 ED

Cisco Catalyst 9000 Series Switches, IOS XE Release 17.15.1 ED 思科 Catalyst 9000 交换产品系列 IOS XE 系统软件 请访问原文链接:https://sysin.org/blog/cisco-catalyst-9000/,查看最新版。原创作品,转载请保留出处。 作者主页&…

如何计算光伏在安装过程中的损耗程度?

光伏系统在实际安装和运营过程中,会受到多种因素的影响,导致电能损耗。这些损耗包括线缆损耗、逆变器效率、组件品质、灰尘积累、入射角损失等。 一、光伏系统损耗的分类 光伏系统的损耗大致可以分为以下几类: 1、线缆损耗:光伏…

响应式网站和自适应网站有什么区别?

响应式网站和自适应网站在技术基础、用户体验以及开发成本等方面存在显著差异。具体分析如下: 响应式网站和自适应网站有什么区别? 技术基础 响应式网站:通过CSS3的媒体查询(Media Query)来检测设备屏幕尺寸,并加载…