当前位置: 首页 > article >正文

redis基本数据结构-set

文章目录

    • 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.kler.cn/news/311134.html

相关文章:

  • 与Linux的初见
  • ISSTA 2024盛大开幕:中国学者的录取数和投稿量均位列第一
  • HarmonyOS学习(十)——网络编程
  • javascript中栈内存与堆内存如何理解?以及如何区分?
  • 力扣150题——多维动态规划
  • Android AlertDialog圆角背景不生效的问题
  • Mybatis 和 数据库连接
  • Redis搭建集群
  • 如何更换OpenHarmony SDK API 10
  • 前端项目使用js将dom生成图片、PDF
  • Linux安装、Nginx反向代理、负载均衡学习
  • 95. UE5 GAS RPG 实现创建多段飞弹攻击敌人
  • C语言——自定义类型
  • Nginx 实现七层的负载均衡
  • 4位整数的数位和
  • OJ在线评测系统 前端开发设计优化通用菜单组件二 调试用户自动登录
  • 面试官:什么是CAS?存在什么问题?
  • 探索RESTful风格的网络请求:构建高效、可维护的API接口【后端 20】
  • AI换脸等违法行为的最关键原因是个人隐私信息的泄露,避免在网络上发布包含个人敏感信息的照片。
  • 图书管理系统(面向对象的编程练习)
  • 高级c语言(一)
  • Mybatis续
  • 36.贪心算法3
  • Android 内置应用裁剪
  • Java集合面试(上)
  • Kafka+PostgreSql,构建一个总线服务
  • k8s 微服务 ingress-nginx 金丝雀发布
  • ESRGAN——老旧照片、视频帧的修复和增强,提高图像的分辨率
  • ozon买家网址是什么,跨境电商ozon买家网址
  • YOLOv8的GPU环境搭建方法