自学内容网 自学内容网

Spring Boot 3.3 【九】Redis 的五种数据结构深入浅出(String & List & Set & Hash & Zset)

如果觉得本文能够帮到您,请关注🌟、点赞👍、收藏📚,让这份美好延续下去!

一、Redis 数据结构简介

在现代应用开发中,高效的数据存储和管理是构建强大系统的关键。Redis 作为一种高性能的内存数据库,以其丰富的数据结构和快速的操作能力而备受青睐。Spring Boot 3 作为流行的开发框架,为整合 Redis 提供了便捷的方式。

在本文中,我们将深入探讨 Spring Boot 3 如何与 Redis 进行整合,并详细介绍对 Redis 的五种主要数据结构 —— 字符串(String)、列表(List)、集合(Set)、哈希(Hash)和有序集合(Sorted Set)的操作。通过掌握这些操作,我们能够充分发挥 Redis 的优势,提升应用的性能和灵活性。

二、深入 Redis 的五种数据结构

(一)字符串

1. Redis String 简介

Redis 的字符串(String)是最基本的数据类型,由一个键和一个值组成,其中键和值都可以是字符串。其值的最大限制为 512MB,这使得它能够存储相对较大的数据量。String 类型是 Redis 中最为常用的数据类型之一,因为它支持多种操作,非常灵活。

它支持简单的 GET 和 SET 操作,可以快速地获取和设置字符串的值。此外,还支持自增(INCR)和自减(DECR)操作,这使得它非常适合用于实现 计数器功能,比如 统计网站的访问量点赞数等。同时,还可以进行字符串拼接等操作,进一步扩展了其应用场景。

典型的应用场景 包括缓存数据,比如 存储用户登录状态Token 以及各种 配置信息等。在这些场景中,String 类型可以快速地存储和读取数据,提高系统的响应速度。另外,结合 SETNX 命令,还可以用字符串来实现简单的 分布式锁,保证在分布式环境下对共享资源的互斥访问。

底层原理方面,Redis 底层对字符串使用的是简单动态字符串(SDS)。SDS 不仅仅是对 C 字符串的简单封装,它还加入了长度属性,使得获取字符串长度的操作时间复杂度为 O (1)。同时,SDS 还采用了空间预留策略,当进行字符串拼接等操作时,可以减少内存分配的次数,提高性能。此外,SDS 支持二进制安全,可以存储文本和二进制数据,这使得它在存储各种类型的数据时更加灵活。

2. redisTemplate.opsForValue().set() 方法

public void setString(String key, String value) {
    redisTemplate.opsForValue().set(key, value);
}

这个方法用于将一个字符串值存储到 Redis 中。通过 redisTemplate.opsForValue() 获取到针对字符串类型的操作对象,然后调用 set 方法,传入键和值,将指定的值存储到 Redis 中,与该键对应。如果键已经存在,会覆盖原有值。

3. redisTemplate.opsForValue().get() 方法

public String getString(String key) {
    return (String) redisTemplate.opsForValue().get(key);
}

此方法用于从 Redis 中获取与给定键对应的值。同样通过 redisTemplate.opsForValue() 获取操作对象,然后调用 get 方法传入键,返回存储在 Redis 中的字符串值。如果键不存在,将返回 null。

在这里插入图片描述

(二)列表

1. Redis List 简介

Redis 的列表(List)是一个 双向链表,可以从头部或尾部插入、删除元素。常用的命令包括 LPUSH(从头部插入元素)、RPUSH(从尾部插入元素)、LPOP(从头部弹出元素)、RPOP(从尾部弹出元素)等。

Redis 的列表还支持阻塞操作,如 BLPOP 和 BRPOP。当列表为空时,这些命令可以阻塞等待,直到有元素被插入到列表中。这种阻塞操作使得列表非常适合用于实现 消息队列 等场景。

典型的应用场景之一是作为消息队列。可以使用 LPUSH 将消息放入队列的头部,使用 RPOP 或 BRPOP 从队列的尾部弹出消息进行处理。这种方式实现的消息队列简单高效,适用于各种异步处理场景。另一个应用场景是 任务调度,在异步任务分发系统中,可以将任务放入列表中,由多个消费者去消费任务,实现任务的并行处理。

列表采用 双向链表(quicklist) 实现。对于较短的列表,Redis 会使用 压缩列表(ziplist) 来节省内存。压缩列表是一种紧凑的 内存数据结构,可以在一定程度上减少内存占用。但是,当列表长度增加时,为了保证操作的时间复杂度,Redis 会自动将其转换为真正的双向链表。这样可以在不同的场景下平衡内存使用和操作效率。

2. redisTemplate.opsForList().leftPush 方法

(1)以下方法将一个元素插入到指定列表的左侧。
使用 redisTemplate.opsForList() 获取针对列表类型的操作对象,调用 leftPush 方法,传入键和要插入的元素值。随着元素不断插入,列表的头部不断变化,最先插入的元素会逐渐移向列表的尾部。

public void leftPush(String key, Object value) {
    redisTemplate.opsForList().leftPush(key, value);
}

(2)依次向 list 中存入元素 aaa,bbb,ccc,aaa,ddd

  @PostMapping("/leftPush")
    public void leftPush() {
    String aaa = "aaa", bbb="bbb", ccc="ccc", ddd="ddd";
        userService.leftPush(aaa);
        userService.leftPush(bbb);
        userService.leftPush(ccc);
        userService.leftPush(aaa);
        userService.leftPush(ddd);
    }

(3)可以看到 redis 中,先存储的在右侧(底部),后存储的在左侧(上部):

在这里插入图片描述

3. redisTemplate.opsForList().rightPush 方法

(1)该方法是从列表 右侧 插入元素:

  public void rightPush(String key, Object value) {
        redisTemplate.opsForList().rightPush(key, value);
    }
  @PostMapping("/rightPush")
    public void rightPush() {
    String eee = "eee", fff="fff";
        userService.rightPush(eee);
        userService.rightPush(fff);
    
    }

(2)查看 redis中 的元素,eeefff 是从列表右侧插入:

在这里插入图片描述

4. 从列表左侧和右侧取出元素

(1)可以从列表左侧或者右侧取出(删除)一个元素。
即使用 leftPoprightPop 方法,从列表的左侧或右侧移除并返回一个元素。如果列表为空,将返回 null。

 /*
     * 从左侧取出一个元素
     */
    public Object leftPop(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }
    
    /*
     * 从右侧取出一个元素
     */
    public Object rightPop(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }

(2)从左侧取出了元素 ddd, 从右侧取出了元素 fff

在这里插入图片描述

5. 获取列表长度

(1)redisTemplate.opsForList().size() 方法:

// 获取列表长度
    public Long listLength(String key) {
        return redisTemplate.opsForList().size(key);
    }

(2)请求结果为 5
在这里插入图片描述

6. 获取列表指定范围的元素

(1)以下方法的作用是获取指定键对应列表中从 start 索引到 end 索引范围内的元素。

它通过调用 redisTemplate.opsForList().range(key, start, end) 实现,可用于获取列表的一部分进行处理或展示,其中列表的第一个元素从 0 开始。

// 获取列表指定范围的元素
    public List<Object> range(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

(2)Redis 列表的原数据如下图所示:

在这里插入图片描述

(3)比如获取列表从索引 1(第二个元素) 开始 至 索引 4

   /*
     * 获取列表指定范围元素
     */
    @GetMapping("range")
    public List<Object> range(String key) {
        return userService.range(key, 1, 4);
    }

得出如下结果:

在这里插入图片描述
(4)获取整个列表
输入起始位置为 0,

/*
     * 获取列表指定元素
     */
    @GetMapping("range")
    public List<Object> range(String key) {
        return userService.range(key, 0, -1);
    }

(三)无序集合

1. Redis Set 简介

Redis 的集合(Set)是一个 无序的唯一的 元素集合。这意味着集合中的元素是唯一的,不会有重复元素。集合提供了类似于数学集合的操作,支持交集(SINTER)、并集(SUNION)、差集(SDIFF)等。

常见的 Redis 操作 包括 SADD 用于向集合中添加元素、SREM 用于从集合中删除元素、SISMEMBER 用于判断一个元素是否在集合中、SMEMBERS 用于获取集合中的所有元素等。

典型的应用场景 之一是 标签系统。可以将用户标签存储为集合,每个集合代表一个用户群体。通过集合的交集、并集和差集等操作,可以方便地找出同时拥有某几个标签的用户,或者找出具有特定标签组合的用户群体。另一个应用场景是 去重功能。在某些场景下,比如 热门搜索词访问日志的去重等,可以通过集合的唯一性特性来避免重复数据的存储和处理。

集合在 小集合 时使用 整数集合(intset) 来存储元素。整数集合是一种紧凑的整数存储结构,可以节省内存。当集合中的元素数量增加或者元素类型不是整数时,会自动转换为 哈希表(hashtable) 实现。通过哈希表的快速查找特性,可以实现 O (1) 的时间复杂度来判断元素是否存在于集合中。

2. 代码示例

Redis Util 工具类的各个方法对应 Redis 集合的不同操作:

package com.jsglxx.redis;

import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class CacheUtil {

    private final RedisTemplate<String, Object> redisTemplate;

    public CacheUtil(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    // 向集合中添加元素
    public void addToSet(String key, Object value) {
        redisTemplate.opsForSet().add(key, value);
    }

    // 从集合中移除元素
    public void removeFromSet(String key, Object value) {
        redisTemplate.opsForSet().remove(key, value);
    }

    // 判断元素是否在集合中
    public boolean isMemberOfSet(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    // 获取集合中的所有元素
    public Set<Object> getSetMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }
    
}
  • addToSet 方法使用 redisTemplate.opsForSet().add(key, value) 向指定键对应的集合中添加元素。
  • removeFromSet 方法使用 redisTemplate.opsForSet().remove(key, value) 从集合中移除指定元素。
  • isMemberOfSet 方法使用 redisTemplate.opsForSet().isMember(key, value) 判断给定元素是否在集合中。
  • getSetMembers 方法使用 redisTemplate.opsForSet().members(key) 获取集合中的所有元素。

3. 测试方法:

在以下测试方法中,首先向两个集合中添加元素,然后分别测试各个方法,输出结果以验证方法的正确性。

public void testSet() {
String setKey1 = "set1";
        String setKey2 = "set2";

        // 向集合 1 添加元素
        cacheUtil.addToSet(setKey1, "element1");
        cacheUtil.addToSet(setKey1, "element2");
        cacheUtil.addToSet(setKey1, "element3");

        // 向集合 2 添加元素
        cacheUtil.addToSet(setKey2, "element2");
        cacheUtil.addToSet(setKey2, "element3");
        cacheUtil.addToSet(setKey2, "element4");

        // 判断元素是否在集合中
        boolean isMember = cacheUtil.isMemberOfSet(setKey1, "element2");
        System.out.println("【元素element2】在【集合set1】中吗? " + isMember);

        // 获取集合中的所有元素
        Set<Object> setMembers1 = cacheUtil.getSetMembers(setKey1);
        System.out.println("【集合set1】中的所有元素: " + setMembers1);

        Set<Object> setMembers2 = cacheUtil.getSetMembers(setKey2);
        System.out.println("【集合set2】中的所有元素: " + setMembers2);
}

4. 结果输出

【元素element2】在【集合set1】中吗? true
【集合set1】中的所有元素: [element1, element2, element3]
【集合set2】中的所有元素: [element2, element3, element4]

(四)Hash

1. Redis 哈希介绍

Redis 的哈希(Hash)是一个 =键值对集合,非常适合用于存储对象。每个键可以有多个字段,每个字段都对应一个值。这种结构使得可以将一个复杂的对象拆分成多个字段进行存储,方便进行管理和查询。

常用的 Redis 操作包括 HSET 用于设置字段的值、HGET 用于获取字段的值、HDEL 用于删除特定字段等。这些操作使得对哈希的管理非常方便,可以根据需要快速地添加、修改和删除字段。

典型的应用场景 之一是 存储用户信息。可以将用户 ID 作为键,用户的各种属性(如姓名、年龄、性别等)作为字段,存储在哈希中。这样可以避免将整个用户对象序列化成字符串进行存储,在查询和更新用户信息时更加高效。另一个应用场景是 配置项管理,将不同的配置项存储在哈希中,方便根据字段名快速访问和更新某个特定的配置。

哈希使用了两种底层数据结构。在 小数据量 时,使用 压缩列表(ziplist) 来存储哈希。压缩列表是一种紧凑的内存数据结构,可以节省内存空间。但是,随着哈希表的增长,当数据量达到一定程度时,会自动转换为 哈希表(hashtable)。哈希表具有更高的查询效率,可以保证在大数据量时仍然能够快速地进行字段的查找和操作。

2. Redis 工具类代码示例

package com.jsglxx.redis;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class CacheUtil {

    private final RedisTemplate<String, Object> redisTemplate;

    public CacheUtil(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    // 设置哈希字段的值
    public void setHashField(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }

    // 获取哈希字段的值
    public Object getHashField(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }

    // 删除哈希字段
    public void deleteHashField(String key, String... fields) {
        redisTemplate.opsForHash().delete(key, fields);
    }

    // 获取哈希的所有字段名
    public Set<Object> getHashKeys(String key) {
        return redisTemplate.opsForHash().keys(key);
    }

    // 获取哈希的所有值
    public List<Object> getHashValues(String key) {
        return redisTemplate.opsForHash().values(key);
    }

    // 获取哈希的所有字段和值
    public Map<Object, Object> getHashAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
}
  • setHashField 方法使用 redisTemplate.opsForHash().put(key, field, value) 将指定的值设置到哈希表中给定的字段。
  • getHashField 方法使用 redisTemplate.opsForHash().get(key, field) 获取哈希表中指定字段的值。
  • deleteHashField 方法使用 redisTemplate.opsForHash().delete(key, fields) 删除哈希表中的一个或多个字段。
  • getHashKeys 方法使用 redisTemplate.opsForHash().keys(key) 获取哈希表中的所有字段名。
  • getHashValues 方法使用 redisTemplate.opsForHash().values(key) 获取哈希表中的所有值。
  • getHashAll 方法使用 redisTemplate.opsForHash().entries(key) 获取哈希表中的所有字段和值,以一个 Map 的形式返回。

3. 测试类代码

在测试类中,首先设置哈希表的字段值,然后分别测试各个方法,输出结果以验证方法的正确性。

public void testHash() {

String hashKey = "myHash";

// 设置哈希字段的值
cacheUtil.setHashField(hashKey, "field1", "value1");
cacheUtil.setHashField(hashKey, "field2", "value2");
cacheUtil.setHashField(hashKey, "field3", "value3");

// 获取哈希字段的值
Object value = cacheUtil.getHashField(hashKey, "field2");
System.out.println("field2 的值为: " + value);

// 删除哈希字段
cacheUtil.deleteHashField(hashKey, "field1");

// 获取哈希的所有字段名
Set<Object> keys = cacheUtil.getHashKeys(hashKey);
System.out.println("Hash表中的所有键: " + keys);

// 获取哈希的所有值
List<Object> values = cacheUtil.getHashValues(hashKey);
System.out.println("Hash表中的所有值: " + values);

// 获取哈希的所有字段和值
Map<Object, Object> allEntries = cacheUtil.getHashAll(hashKey);
System.out.println("hash表中的所有键值对: " + allEntries);
}

4. 输出结果

field2 的值为: value2
Hash表中的所有键: [field2, field3]
Hash表中的所有值: [value2, value3]
hash表中的所有键值对: {field2=value2, field3=value3}

(五)有序集合

1. Redis Zset 简介

Redis 的有序集合是一种特殊的集合,其中 每个元素都关联一个分数。集合中的元素会按照 分数排序。支持的操作包括 ZADD 用于向有序集合中添加元素并指定分数、ZRANGE 和 ZREVRANGE 分别用于按照分数从小到大和从大到小获取元素、ZCOUNT 用于统计分数范围内的元素数量等。

典型的应用场景之一是 排行榜。比如在游戏中,可以将玩家的分数作为有序集合的元素分数,玩家 ID 作为元素,通过 ZADD 添加玩家及其分数。然后可以使用 ZRANGE 或 ZREVRANGE 获取排名靠前的玩家。另一个应用场景是 延迟任务。可以通过设置元素的分数为任务执行的时间,将任务存储在有序集合中。然后按照时间从集合中取出需要执行的任务进行处理。

有序集合底层使用的是 跳表(Skiplist)哈希表 相结合的数据结构。跳表是一种高效的有序数据结构,它使有序集合支持快速的范围查询和插入操作,时间复杂度为 O (log n)。而哈希表则保证了元素的快速定位,使得可以在 O (1) 的时间复杂度内判断元素是否存在于有序集合中。这种结合的方式既保证了查询效率,又能够有效地管理元素的顺序。

2. Redis 工具类代码示例

package com.jsglxx.redis;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class CacheUtil {

    private final RedisTemplate<String, Object> redisTemplate;

    public CacheUtil(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }  

    // 添加成员到有序集合
    public void addToSortedSet(String key, double score, Object member) {
        redisTemplate.opsForZSet().add(key, member, score);
    }

    // 从有序集合中移除成员
    public void removeFromSortedSet(String key, Object... members) {
        redisTemplate.opsForZSet().remove(key, members);
    }

    // 获取成员的分值
    public Double getScore(String key, Object member) {
        return redisTemplate.opsForZSet().score(key, member);
    }

    // 获取有序集合指定范围的成员(从小到大排序)
    public Set<Object> getRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    // 获取有序集合指定范围的成员(从大到小排序)
    public Set<Object> getReverseRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }

    // 获取分值范围内的成员(从小到大排序)
    public Set<Object> getRangeByScore(String key, double minScore, double maxScore) {
        return redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);
    }

    // 获取分值范围内的成员(从大到小排序)
    public Set<Object> getReverseRangeByScore(String key, double maxScore, double minScore) {
        return redisTemplate.opsForZSet().reverseRangeByScore(key, maxScore, minScore);
    }

    // 获取有序集合的成员数量
    public Long getCardinality(String key) {
        return redisTemplate.opsForZSet().zCard(key);
    }

    // 获取分值范围内的成员数量
    public Long getCountByScore(String key, double minScore, double maxScore) {
        return redisTemplate.opsForZSet().count(key, minScore, maxScore);
    }
    
}
  • addToSortedSet 方法使用 redisTemplate.opsForZSet().add(key, member, score) 向有序集合添加成员和分值。
  • removeFromSortedSet 方法使用 redisTemplate.opsForZSet().remove(key, members) 从有序集合中移除指定成员。
  • getScore 方法使用 redisTemplate.opsForZSet().score(key, member) 获取指定成员的分值。
  • getRangegetReverseRange 方法分别用于获取有序集合指定范围的成员,按照分值从小到大和从大到小排序。
  • getRangeByScoregetReverseRangeByScore 方法用于获取分值范围内的成员,同样。按照分值从小到大和从大到小排序。
  • getCardinality 方法使用 redisTemplate.opsForZSet().zCard(key) 获取有序集合的成员数量。
  • getCountByScore 方法使用 redisTemplate.opsForZSet().count(key, minScore, maxScore) 获取分值范围内的成员数量。

3. 测试类代码

在测试类中,首先向有序集合添加成员,然后分别测试各个方法,输出结果以验证方法的正确性。最后,从有序集合中移除一个成员。

public void testSortedSetOperations() {
      String sortedSetKey = "mySortedSet";

       // 添加成员到有序集合
       cacheUtil.addToSortedSet(sortedSetKey, 10.0, "member1");
       cacheUtil.addToSortedSet(sortedSetKey, 20.0, "member2");
       cacheUtil.addToSortedSet(sortedSetKey, 15.0, "member3");

       // 获取成员的分值
       Double score = cacheUtil.getScore(sortedSetKey, "member2");
       System.out.println("member2 的分值: " + score);

       // 获取有序集合指定范围的成员(从小到大排序)
       Set<Object> range = cacheUtil.getRange(sortedSetKey, 0, 1);
       System.out.println("有序集合ascending(0-1)的成员从小到大排序: " + range);

       // 获取有序集合指定范围的成员(从大到小排序)
       Set<Object> reverseRange = cacheUtil.getReverseRange(sortedSetKey, 0, 1);
       System.out.println("有序集合ascending(0-1)的成员从大到小排序: " + reverseRange);

       // 获取分值范围内的成员(从小到大排序)
       Set<Object> rangeByScore = cacheUtil.getRangeByScore(sortedSetKey, 12.0, 18.0);
       System.out.println("ascending集合内分值在12.0 - 18.0之间的成员: " + rangeByScore);

       // 获取分值范围内的成员(从大到小排序)
       Set<Object> reverseRangeByScore = cacheUtil.getReverseRangeByScore(sortedSetKey, 12.0, 18.0);
       System.out.println("分值12.0 - 18.0之间的成员从大到小排序: " + reverseRangeByScore);

       // 获取有序集合的成员数量
       Long cardinality = cacheUtil.getCardinality(sortedSetKey);
       System.out.println("集合内成员数量: " + cardinality);

       // 获取分值范围内的成员数量
       Long countByScore = cacheUtil.getCountByScore(sortedSetKey, 10.0, 20.0);
       System.out.println("10.0-20.0分值范围内的成员数量: " + countByScore);

       // 从有序集合中移除成员
       cacheUtil.removeFromSortedSet(sortedSetKey, "member1");
   }

4. 输出结果

member2 的分值: 20.0
有序集合ascending(0-1)的成员从小到大排序: [member1, member3]
有序集合ascending(0-1)的成员从大到小排序: [member2, member3]
ascending集合内分值在12.0 - 18.0之间的成员: [member3]
分值12.0 - 18.0之间的成员从大到小排序: [member3]
集合内成员数量: 3
10.0-20.0分值范围内的成员数量: 3

三、结尾

总之,Spring Boot 3 与 Redis 的整合为开发者提供了强大的数据存储和处理能力。通过对 Redis 的五种数据结构的熟练操作,我们可以根据不同的业务需求选择合适的数据结构,实现高效的数据管理和快速的访问。无论是缓存数据、构建消息队列、存储用户信息还是进行复杂的排序操作,Redis 都能在 Spring Boot 3 应用中发挥重要作用。不断探索和优化 Redis 的使用,将有助于我们构建更加健壮、高效的应用程序,满足不断变化的业务需求。

🌟 对技术管理感兴趣 请扫码关注下方 ⬇ 【 技术管理修行】


原文地址:https://blog.csdn.net/wcblog/article/details/143430527

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!