详解Redis的五种基本数据类型(String、List、Hash、Set、ZSet)

1.概述

Redis共有五种基本数据类型:String(字符串)、List(列表)、Hash(散列)、Set(集合)、ZSet(有序集合)。这些基础数据结构支持丰富的原子操作,底层通过内存压缩算法(如ziplist、intset)实现空间与速度的平衡。本文将详细介绍五种基础数据结构,帮助大家更好地理解Redis基础数据的使用及原理。

2.基本数据类型详解

2.1 String

String类型是Redis中使用最多的类型,key是唯一标识,value代表对应key存储的值,value可以是字符串、数字(整型或浮点数),value最多可以容纳的数据长度是512M。

2.1.1 String类型常用指令

命令 说明
SET key value 设置指定 key 的值
GET key 获取指定 key 的值
MSET key1 value1 key2 value2 ...... 批量设置key、value
MGET key1 key2 ... 获取一个或多个指定 key 的值
STRLEN key 返回key所存储的字符串长度
SETNX key value key 不存在时设置 key 的值
SETEX key seconds value 设置key、value和过期时间
INCR key 将 key 中储存的数字值增一
DECR key 将 key 中储存的数字值减一
APPEND key value 向当前key的字符串后面追加字符串,key不存在相当于set key value
GETRANGE key start end 截取key以start为起点,end为终点的字符串
KEYS * 获取当前数据库所有的key(通用指令)
EXISTS key 判断指定 key 是否存在(通用指令)
DEL key 删除key(通用指令)
GETSET key value 先get后set

2.1.2 指令实测

bash 复制代码
> set books java
OK
> get books
java
> MSET name zhangsan age 18
OK
> mget name age
zhangsan
18
> STRLEN name
8
> SETNX name zhangsan
0
> SETEX address 30 beijing
OK
> TTL address
18
> INCR age
19
> INCR age
20
> GET age
20
> DECR age
19
> APPEND name ',hello world'
20
> GET name
zhangsan,hello world
> GETRANGE name 0 4
zhang
> GETRANGE name 0 3
zhan
> keys *
name
key1
count
books
key2
age
> EXISTS count
1
> del count
1
> GETSET db redis
null
> GET db
redis

2.1.3 应用场景

1.‌计数器

例如,可以用来记录网站的访问次数、用户登录次数等。

使用场景:使用 INCR 和 DECR 指令对计数器进行递增或递减操作,实现记录网站的访问次数、用户登录次数等。
‌2.缓存功能

例如,存储用户信息、配置信息等。

使用场景:使用 SET 和 GET 指令实现简单的键值对缓存。
‌3.分布式锁

例如:在分布式系统中确保某个操作在同一时间内只能由一个实例执行。

使用场景:使用 SET 指令的 NX(仅当键不存在时设置)和 EX(设置过期时间)选项实现分布式锁。

2.2 List

2.2.1 List类型常用指令

命令 说明
LPUSH key value1 value2 ... 在指定列表的头部(左边)添加一个或多个元素
RPUSH key value1 value2 ... 在指定列表的头部(右边)添加一个或多个元素
LSET key index value 将指定列表索引 index 位置的值设置为 value
LPOP key 移除并获取指定列表的第一个元素(最左边)
RPOP key 移除并获取指定列表的最后一个元素(最右边)
LLEN key 获取列表元素数量
LRANGE key start end 获取列表 start 和 end 之间 的元素

2.2.2 List指令实测

bash 复制代码
> LPUSH books java python go
3
> LLEN books
3
> LRANGE books 0 -1
go
python
java
> LPUSH books javascript
4
> LRANGE books 0 -1
javascript
go
python
java
> RPUSH books c
5
> LRANGE books 0 -1
javascript
go
python
java
c
> RPOP books
c
> LRANGE books 0 -1
javascript
go
python
java
> LPOP books
javascript
> LRANGE books 0 -1
go
python
java
> LINDEX books 0
go
> LINDEX books 2
java
> LSET books 2 javascript
OK
> LRANGE books 0 -1
go
python
javascript

2.2.3 使用场景

1.‌简单消息队列

例如,通过订阅同一个list的key实现消息队列等。

使用场景:通过LPUSH/RPOP或者RPUSH/LPOP可以实现简易消息队列,实现数据先进先出。
‌2.模拟栈实现

例如,通过模拟栈先进后出。

使用场景:通过LPUSH/LPOP或者RPUSH/RPOP可以实现栈,实现数据后进先出,实现特点场景下的规则解析。

2.3 Hash

2.3.1 Hash类型常用指令

命令 说明
HSET key field value 设置指定哈希表中指定字段的值
HGET key field 获取指定哈希表中指定字段的值
HMSET key field1 value1 field2 value2 ... 同时设置一个或多个 field-value 到指定哈希表中
HMGET key field1 field2 ... 获取指定哈希表中一个或者多个指定字段的值
HGETALL key 获取指定哈希表中所有的键值对
HEXISTS key field 查看指定哈希表中指定的字段是否存在
HDEL key field1 field2 ... 删除一个或多个哈希表字段
HLEN key 获取指定哈希表中字段的数量
HSETNX key field value 当指定哈希表中的字段不存在时,才添加值
HINCRBY key field increment 对指定哈希中的指定字段做运算操作(正数为加,负数为减)

2.3.2 Hash指令实测

bash 复制代码
> HSET userInfo-1 name zhangsan
1
> HSET userInfo-1 age 18
1
> HSET userInfo-1 sex male
1
> HGET userInfo-1 name
zhangsan
> HMSET userInfo-2 name lisi age 20 sex female
OK
> HMGET userInfo-1 name age sex
zhangsan
18
male
> HGETALL userInfo-1
name
zhangsan
age
18
sex
male
> HEXISTS userInfo-1 name
1
> HDEL userInfo-1 sex
1
> HGETALL userInfo-1
name
zhangsan
age
18
> HLEN userInfo-1
2
> HSETNX userInfo-1 name zhangsan
0
> HINCRBY userInfo-1 age 5
23
> HGETALL userInfo-1
name
zhangsan
age
23

2.3.2 Hash使用场景

1.‌对象信息存储

例如,存储用户信息等。

使用场景:以信息标签+用户唯一id作为key,属性分别是作为field,这样相对于String存储的优势是提升了效率(string类型需要进行数据转换后才能获取到值)。
‌2.购物车功能

例如,实现购物车功能

使用场景:将用户id作为key,商品id作为field,field的值为商品数量。

2.4 Set

2.4.1 Set类型常用指令

命令 说明
SADD key member1 member2 ... 向指定集合添加一个或多个元素
SMEMBERS key 获取指定集合中的所有元素
SCARD key 获取指定集合的元素数量
SREM key memeber 移除集合中的指定元素
SISMEMBER key member 判断指定元素是否在指定集合中
SINTER key1 key2 ... 获取给定所有集合的交集
SINTERSTORE destination key1 key2 ... 将给定所有集合的交集存储在 destination 中
SUNION key1 key2 ... 获取给定所有集合的并集
SUNIONSTORE destination key1 key2 ... 将给定所有集合的并集存储在 destination 中
SDIFF key1 key2 ... 获取给定所有集合的差集
SDIFFSTORE destination key1 key2 ... 将给定所有集合的差集存储在 destination 中
SPOP key count 随机移除并获取指定集合中一个或多个元素
SRANDMEMBER key count 随机获取指定集合中指定数量的元素

2.4.2 Set指令实测

bash 复制代码
> sadd db oracle mysql sqlite
3
> SADD db mysql
0
> SMEMBERS db
oracle
mysql
sqlite
> SCARD db
3
> SISMEMBER db oracle
1
> SREM db mysql
1
> SMEMBERS db
oracle
sqlite
> SADD db1 mysql oracle hbase
3
> SINTERSTORE db2 db db1
1
> SMEMBERS db2
oracle
> SUNION db db1
oracle
sqlite
mysql
hbase
> SDIFF db db1
sqlite
> SRANDMEMBER db1 2
oracle
hbase
> SPOP db1 2
oracle
hbase
> SMEMBERS db1
mysql

2.4.3 Set使用场景

1.‌共同关注好友

例如,微博、B站等共同关注博主等。

使用场景:可以将A用户的关注博主、B用户的关注博主分别做一个Set集合,通过取并集获取共同关注对象。
‌2.获取随机值

例如,实现某些具体场景随机值获取

使用场景:将用户id作为一个Set集合,通过SPOP指令随机获取用户id,实现抽奖等场景功能。
‌3.快速去重

在某些应用中,可以使用有序集合来管理定时任务,其中任务的执行时间作为分数存储。

使用场景:获取网站UV数据,将网站域名作为key,用户唯一标识作为集合值,实现快速去重,获取当日、周该网站UV数据。

2.5 ZSet

2.5.1 ZSet类型常用指令

有序集合相对于Set增加了一个权重参数score,集合中的元素能够按照score进行有序排列,也可以按照score的范围来获取集合中的元素。

命令 说明
ZADD key score1 member1 score2 member2 ... 向指定有序集合中添加一个或多个元素
ZSCORE key member 获取有序集合中指定元素的score值
ZCARD KEY 获取指定有序集合的元素数量
ZRANGEBYSCORE key min max [WITHSCORES] 根据分数获取有序集合中元素
ZREVRANK key member 返回有序集中成员的排名,排名以0为底,分数值最大的成员排名为0
ZLEXCOUNT key min max 对于一个所有成员的分值都相同的有序集合键 key 来说, 这个命令会返回该集合中, 成员介于 min 和 max 范围内的元素数量。
ZINTERSTORE destination numkeys key1 key2 ... 将给定所有有序集合的交集存储在destination中,对相同元素对应的score值进行sum聚合操作,numkeys 为集合数量
ZUNIONSTORE destination numkeys key1 key2 ... 求并集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集(结果集)储存到 destination
ZDIFFSTORE destination numkeys key1 key2 ... 求差集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集(结果集)储存到 destination
ZRANGE key start end 获取指定有序集合 start 和 end 之间的元素,score由低到高排序
ZREVRANGE key start end 获取指定有序集合 start 和 end 之间的元素,score由低到高排序
ZREM key member 移除有序集合中指定元素

2.5.3 ZSet指令实测

bash 复制代码
> zadd salary 3000 zhangsan 4000 lisi 8000 wangwu
3
> ZSCORE salary zhangsan
3000
> ZADD salary 12000 zhaoliu
1
> ZCARD salary
4
> ZRANGEBYSCORE salary 3000 5000
zhangsan
lisi
> ZRANGEBYSCORE salary 3000 5000 WITHSCORES
zhangsan
3000
lisi
4000
> ZREVRANK salary zhaoliu
0
> ZREVRANK salary wangwu
1
> ZINTERSTORE salary2 2 salary salary1
1
> ZRANGE salary 0 -1 withscores
zhangsan
3000
lisi
4000
wangwu
8000
> ZRANGE salary2 0 -1 withscores
zhangsan
6000
> ZUNIONSTORE salary3 2 salary salary1
5
> ZRANGE salary3 0 -1 withscores
lisi
4000
tony
5000
tom
6000
zhangsan
6000
wangwu
8000
> ZDIFFSTORE salary4 2 salary salary1
2
> ZRANGE salary4 0 -1 withscores
lisi
4000
wangwu
8000
> ZREVRANGE salary 0 -1
wangwu
lisi
zhangsan
> ZREM salary zhangsan
1

2.5.3 ZSet使用场景

1.‌排行榜系统‌

例如,游戏中的玩家分数排行榜、视频网站上的视频点赞数排行榜等。

使用场景:可以实时更新分数,并利用 ZADD 命令添加或更新元素及其分数,使用 ZREVRANGE 或 ZREVRANGEBYSCORE 命令获取排名靠前的元素。
‌2.延迟消息队列‌

使用有序集合存储消息及其延迟时间(以时间戳或相对延迟时间表示),然后通过 ZRANGEBYSCORE 命令获取当前时间之前的所有消息进行处理。

使用场景:可以确保消息按照预定的延迟时间被处理,非常适合需要延迟处理的场景。
‌3.定时任务调度‌

在某些应用中,可以使用有序集合来管理定时任务,其中任务的执行时间作为分数存储。

使用场景:通过定期查询当前时间之前的任务并执行它们,可以实现一个简单的任务调度器。

3.代码实现

3.1 pom文件引入

java 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

3.2 RedisUtil工具类实现

java 复制代码
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

    private static final Logger LOG = LoggerFactory.getLogger(RedisUtil.class);
    /**
     * 默认过期时间,单位:秒,即,24个小时 后 过期
     */
    public static final long DEFAULT_EXPIRE = 60 * 60 * 24;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource(name = "strRedisTemplate")
    private RedisTemplate<String, String> stringRedisTemplate;

    /**
     * set 方法
     *
     * @param key   key
     * @param value value
     */
    public void set(String key, Object value) {
        set(key, value, null);
    }

    public void setWithDefaultExpire(String key, Object value) {
        set(key, value, DEFAULT_EXPIRE);
    }


    /**
     * @param key    redis key
     * @param value  redis值
     * @param expire 过期时间,秒
     */
    public void set(String key, Object value, Long expire) {
        if (expire != null && expire.longValue() != 0L) {
            redisTemplate.boundValueOps(key).set(value, expire, TimeUnit.SECONDS);
        } else {
            redisTemplate.boundValueOps(key).set(value);
        }
    }

    /**
     * @param key    redis key
     * @param value  redis值
     * @param expire 过期时间,秒
     */
    public void setString(String key, String value, Long expire) {
        if (expire != null && expire.longValue() != 0L) {
            stringRedisTemplate.boundValueOps(key).set(value, expire, TimeUnit.SECONDS);
        } else {
            stringRedisTemplate.boundValueOps(key).set(value);
        }
    }

    public Double increment(String key, double score) {
        return redisTemplate.opsForValue().increment(key, score);
    }

    /**
     * 获取redis value
     *
     * @param key
     * @return Object 对象
     */
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 判断key是否存在
     *
     * @param key
     * @return Set 集合
     */
    public Set<String> keys(String key) {
        return redisTemplate.keys(key);
    }

    public void deleteKeys(Set<String> keys) {
        redisTemplate.delete(keys);
    }


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

    public void delete(String key) {
        redisTemplate.delete(key);
    }

    public void expire(String key, Long expire) {
        redisTemplate.expire(key, expire, TimeUnit.SECONDS);
    }

    public Boolean hsetAbsent(String key, String hkey, Object value) {
        return redisTemplate.opsForHash().putIfAbsent(key, hkey, value);
    }

    public void hset(String key, String hkey, Object value) {
        redisTemplate.opsForHash().put(key, hkey, value);
    }

    public void hmset(String key, Map<?, ?> hashMap) {
        redisTemplate.opsForHash().putAll(key, hashMap);
    }

    public Long lpushString(String key, String val) {
        return stringRedisTemplate.boundListOps(key).leftPush(val);
    }

    public String rpopString(String key) {
        return stringRedisTemplate.boundListOps(key).rightPop();
    }

    public Long llen(String key) {
        return stringRedisTemplate.boundListOps(key).size();
    }

    public Properties info() {
        RedisConnection connection = null;
        Properties p = null;
        try {
            connection = redisTemplate.getConnectionFactory().getConnection();
            p = connection.info("memory");
        } catch (Exception e) {
            LOG.error("redis获取连接失败", e);
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return p;
    }

    public String clients() {
        List<RedisClientInfo> clientList = redisTemplate.getClientList();
        return JSON.toJSONString(clientList);
    }

    /**
     * 左边入队
     */
    public Long lpush(String key, Object val) {
        return redisTemplate.boundListOps(key).leftPush(val);
    }

    /**
     * 右边出队
     */
    public Object rpop(String key) {
        return redisTemplate.boundListOps(key).rightPop();
    }

    /**
     * 右边出队
     */
    public String rpop(String key, Integer timeout) {
        return stringRedisTemplate.opsForList().rightPop(key, timeout, TimeUnit.SECONDS);
    }

    public Object hget(String key, String hkey) {
        return redisTemplate.opsForHash().get(key, hkey);
    }

    public String hgetStr(String key, String hkey) {
        return (String) stringRedisTemplate.opsForHash().get(key, hkey);
    }

    public Long hdel(String key, String hkey) {
        return redisTemplate.opsForHash().delete(key, hkey);
    }

    public void hincrement(String key, String hkey) {
        redisTemplate.opsForHash().increment(key, hkey, 1);
    }

    public boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    public Double zscore(String key, Object val) {
        return stringRedisTemplate.opsForZSet().score(key, val);
    }

    public boolean zadd(String key, String value, double score) {
        return stringRedisTemplate.opsForZSet().add(key, value, score);
    }

    public Long zadd(String key, Set<ZSetOperations.TypedTuple<String>> tuples) {
        return stringRedisTemplate.opsForZSet().add(key, tuples);
    }

    public void zaddObj(String key, Object value, double score) {
        redisTemplate.opsForZSet().add(key, value, score);
    }

    public Long removeRangeByScoreObj(String key, double minScore, double maxScore) {
        return redisTemplate.opsForZSet().removeRangeByScore(key, minScore, maxScore);
    }

    public Long removeRangeByObj(String key, Object value) {
        return redisTemplate.opsForZSet().remove(key, value);
    }

    public Long removeRangeByScoreStr(String key, double minScore, double maxScore) {
        return stringRedisTemplate.opsForZSet().removeRangeByScore(key, minScore, maxScore);
    }

    public Double zStringScore(String key, Object value) {
        return stringRedisTemplate.opsForZSet().score(key, value);

    }

    public Set<String> zrangeByScore(String key, double minScore, double maxScore) {
        return stringRedisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);
    }

    public Set<String> zreverseRangeByScore(String key, double minScore, double maxScore) {
        return stringRedisTemplate.opsForZSet().reverseRangeByScore(key, minScore, maxScore);
    }

    public Set<String> zrangeByScore(String key, double minScore, double maxScore, long offset, long count) {
        return stringRedisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore, offset, count);
    }

    public Set<String> zreverseRangeByScore(String key, double minScore, double maxScore, long offset, long count) {
        return stringRedisTemplate.opsForZSet().reverseRangeByScore(key, minScore, maxScore, offset, count);
    }

    public Set zrangeByScoreObj(String key, double minScore, double maxScore) {
        return redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);
    }

    public Long zrank(String key, String value) {
        return stringRedisTemplate.opsForZSet().rank(key, value);
    }

    public void zremObj(String key, String member) {
        redisTemplate.opsForZSet().remove(key, member);
    }

    public Long zremStr(String key, String member) {
        return stringRedisTemplate.opsForZSet().remove(key, member);
    }

    public Map<Object, Object> hGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    public Set<Object> hkeys(String key){
        return redisTemplate.opsForHash().keys(key);
    }

    public List<Object> hValues(String key) {
        return redisTemplate.opsForHash().values(key);
    }


    public Map<String, String> hGetAllConvertString(String key) {
        Map<Object, Object> tmp = redisTemplate.opsForHash().entries(key);
        return tmp != null ? convertToString(tmp) : null;
    }

    public Set<ZSetOperations.TypedTuple<Object>> rangeByScoreWithScores(String name, double min, double max, long offset, long count) {
        return redisTemplate.opsForZSet().rangeByScoreWithScores(name, min, max, offset, count);
    }

    public Set<ZSetOperations.TypedTuple<String>> strRangeByScoreWithScores(String name, double min, double max, long offset, long count) {
        return stringRedisTemplate.opsForZSet().rangeByScoreWithScores(name, min, max, offset, count);
    }

    public Set<String> strZRange(String key, int start, int end) {
        return stringRedisTemplate.opsForZSet().range(key, start, end);
    }

    public Set<ZSetOperations.TypedTuple<String>> strZRangeWithScores(String key, int start, int end) {
        return stringRedisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }

    public Double strIncrementScore(String key, String value, double delta) {
        return stringRedisTemplate.opsForZSet().incrementScore(key, value, delta);
    }

    public Double incrementScore(String key, String value, double score) {
        return redisTemplate.opsForZSet().incrementScore(key, value, score);
    }

    public Set<String> members(String key) {
        return stringRedisTemplate.opsForSet().members(key);
    }

    public boolean isMembers(String key, String value) {
        return stringRedisTemplate.opsForSet().isMember(key, value);
    }

    public Set<String> sMembersStr(String key) {
        return stringRedisTemplate.opsForSet().members(key);
    }

    public void sAddStr(String key, String value) {
        stringRedisTemplate.opsForSet().add(key, value);
    }

    public void sAdd(String key, Object val) {
        redisTemplate.opsForSet().add(key, val);
    }

    public String sPop(String key) {
        return stringRedisTemplate.opsForSet().pop(key);
    }

    public void leftPush(String key, String value) {
        stringRedisTemplate.boundListOps(key).leftPush(value);
    }

    public void sRemoveStr(String key, String value) {
        stringRedisTemplate.opsForSet().remove(key, value);
    }

    public void publish(String channel, String value) {
        stringRedisTemplate.convertAndSend(channel, value);
    }


    public static Map<String, String> convertToString(Map<Object, Object> map) {
        Objects.requireNonNull(map);
        Map<String, String> result = new ConcurrentHashMap<>(map.size());
        map.forEach((key, value) -> result.put(key.toString(), value.toString()));
        return result;
    }
}

4.小结

1.本文主要讲解redis的基础数据类型和使用方式,同时实操指令,说明其使用场景;

2.本文利用JAVA语言实现了个Redis工具类,对RedisTemplate做了二次封装,供大家参考;

3.关于Redis的底层数据结构,数据存储原理,本文没有详细叙述,可参考文献部分,写的都十分详细。

5.参考文献

1.https://www.redis.net.cn/

2.https://cloud.tencent.com/developer/article/2508467

3.https://juejin.cn/post/7525640395234230326

相关推荐
济南java开发,求内推5 小时前
redis升级至7.0.15版本
redis
摇滚侠5 小时前
Redis 零基础到进阶,Redis 持久化,RDB,AOF,RDB AOF 混合,笔记 28-46
数据库·redis·笔记
叫我龙翔5 小时前
【Redis】从零开始掌握redis --- 认识redis
数据库·redis·缓存
斯普信专业组5 小时前
Redis 持久化及应用场景详解
redis
源代码•宸5 小时前
goframe框架签到系统项目(安装 redis )
服务器·数据库·经验分享·redis·后端·缓存·golang
忍冬行者15 小时前
清理三主三从redis集群的过期key和键值超过10M的key
数据库·redis·缓存
TimberWill15 小时前
使用Redis队列优化内存队列
数据库·redis·缓存
川石课堂软件测试17 小时前
Mysql中触发器使用详详详详详解~
数据库·redis·功能测试·mysql·oracle·单元测试·自动化
gugugu.19 小时前
Redis Set类型完全指南:无序集合的原理与应用
数据库·windows·redis