首页 > 数据库 >redis基本数据结构-set

redis基本数据结构-set

时间:2024-09-15 12:23:31浏览次数:3  
标签:set return err nil tags redis 数据结构 好友 string

文章目录

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中的元素是唯一的,任何尝试添加重复元素的操作都会无效。
  • 使用集合运算时,需要注意结果集的大小,因为它可能会影响性能。

标签:set,return,err,nil,tags,redis,数据结构,好友,string
From: https://blog.csdn.net/qq_40840571/article/details/142275945

相关文章

  • k8s部署单机redis pod
     kind:ConfigMapapiVersion:v1metadata:name:redisnamespace:defaultlabels:app:redisdata:redis.conf:|-port6379bind0.0.0.0dir/dataappendonlyyesprotected-modenorequirepassredispidfile/data/red......
  • 关于JS解构数据结构的表现形式
    对数组或对象类数据结构,尤其是复杂的JOSN数据结构,要从中取出想要的个别数据,往往会采用遍历方法进行,即麻烦又增加了运行时间。从ES6开始,JS增加了解构进行简化,本质上是打散复杂的数据结构,将其折分为更小的部分(复制出的小结构),从而使用数据更为方便快捷。一、对象解构1.单层对......
  • 云服务器安装redis
    第一步:上传redis压缩安装包到服务器       wgethttps://download.redis.io/releases/redis-5.0.4.tar.gz第二步:将压缩安装包解压        tar-xvfredis-xxx.tar.gz第三步:进入redis的目录,编译redis,执行命令:make        cdr......
  • Redis实现发布/订阅功能(实战篇)
    前言博主在学习Redis实现发布订阅功能的时候,踩了太多的坑。不是讲解不详细,看的一知半解;就是代码有问题,实际压根跑不起来!于是博主萌生了自己写一个最新版且全程无错的博客供各位参考。希望各位不要把我才过的坑再踩一遍。(实战篇的所有代码均由本人测试,全程无Bug。)废话不多......
  • 数据结构实验第一周
    6-1差距几何排序的话复杂度要O(n),可以选择桶排序或者计数排序,我选择的是计数排序比如是32144786我开一个数组a[9](因为最大为8),然后分别对出现的数计数有a:111201110然后按顺序放回就是12344678intfun(intD[],intN){if(N<2)return0;......
  • Redis运维之监控指标,性能监控,监控方式,响应慢分析
    目录1Redis监控1.1Redis监控指标1.1.1性能指标:Performance1.1.2内存指标:Memory1.1.3基本活动指标:Basicactivity1.1.4持久性指标:Persistence1.1.5错误指标:Error1.2监控方式1.2.1info1.2.2性能监控1.2.3内存监控1.2.4基本活动指标1.2.5持久性指标1.2.6错误指标......
  • JAVA进阶-set,Comparable排序,数据结构-树
    day07-set,Comparable排序,数据结构-树泛型Set概述和特点TreeSet集合概述和特点Comparable排序自然排序Comparable的使用使用空参构造创建TreeSet集合自定义Student类实现Comparable接口重写里面的comparaTo方法自然排序简单原理图比较器排序Compara......
  • Redis集群
    Redis集群一、定义由于数据量过大,单个master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展,每个复制集只负责存储整个数据集的一部分,这就是redis的集群。Redis集群是一个提供在多个redis节点间共享数据的程序集,可以支持多个masterRedis集群支持多个master,每个mast......
  • TDengine 建模实战:手把手教你高效设计数据结构
    ✨作为一款高效简洁的大数据平台,TDengine的使用体验极为极为流畅,用户可以轻松实现数据的实时采集、存储与分析,快速获取所需的信息和洞察。但在追求最佳实践的过程中,我们仍需关注一些关键问题。例如,多个设备是否应该向同一个子表写入数据?在数据列过滤查询与基于标签的过滤查询之间,......
  • SpringBoot集成Redis以及StringRedisTemplate的使用
    引言该篇文章介绍了在SpringBoot项目中,集成了Redis,Redis提供了RedisTemplate的扩展StringRedisTemplate,用于对字符串的操作,使用StringRedisTemplate,不需要(写RedisConfig)额外设置序列化。不过存取对象需要自己序列化、反序列化。本文会介绍配置,以及连接Redis,还有使用的部分代......