Redis-06Redis数据结构--集合Set

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: Redis-06Redis数据结构--集合Set

概述


Redis 的集合不是一个线性结构,而是一个哈希表结构,它的内部会根据 hash 分子来存储和查找数据,理论上一个集合可以存储2的32次方减一(约42亿)个元素。


因为采用哈希表结构,所以对于 Redis 集合的插入、删除和查找的复杂度都是 0(1),只是我们需要注意 3 点

  1. 对于集合而言,它的每一个元素都是不能重复的,当插入相同记录的时候都会失败
  2. 集合是无序的
  3. 集合的每一个元素都是 String 数据结构类型


常用集合命令


官网: https://redis.io/commands#set



20190109233114273.png


Redis 的集合可以对于不同的集合进行操作,比如求出两个或者以上集合的交集、 差集和并集等


image.png

image.png


上述命令的前缀都包含 了 一个 s,用来表达这是集合的命令 , 集合是无序的 , 并且支持并集 、 交集和差集的运算。

下面通过命令行客户端来演示这些命令

127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> SADD set1 v1 v2 v3 v4 v5 v6
(integer) 6
127.0.0.1:6379> SADD set2 v0 v2 v4 v6 v8
(integer) 5
127.0.0.1:6379> SCARD set1
(integer) 6
127.0.0.1:6379> SDIFF set1 set2
1) "v5"
2) "v1"
3) "v3"
127.0.0.1:6379> SINTER set1 set2
1) "v4"
2) "v6"
3) "v2"
127.0.0.1:6379> SISMEMBER set2 v2
(integer) 1
127.0.0.1:6379> SISMEMBER set1 v2
(integer) 1
127.0.0.1:6379> SISMEMBER set v2
(integer) 0
127.0.0.1:6379> SMEMBERS set2
1) "v4"
2) "v8"
3) "v6"
4) "v0"
5) "v2"
127.0.0.1:6379> SPOP set1
"v5"
127.0.0.1:6379> SMEMBERS set1
1) "v4"
2) "v6"
3) "v1"
4) "v2"
5) "v3"
127.0.0.1:6379> SRANDMEMBER set1 2
1) "v4"
2) "v3"
127.0.0.1:6379> SREM set1 v1
(integer) 1
127.0.0.1:6379> SMEMBERS set1
1) "v4"
2) "v6"
3) "v2"
4) "v3"
127.0.0.1:6379> SUNION set1 set2
1) "v4"
2) "v8"
3) "v6"
4) "v0"
5) "v2"
6) "v3"
127.0.0.1:6379> SMEMBERS set1
1) "v4"
2) "v6"
3) "v2"
4) "v3"
127.0.0.1:6379> SMEMBERS set2
1) "v4"
2) "v8"
3) "v6"
4) "v0"
5) "v2"
127.0.0.1:6379> 


交集、并集和差集保存命令的用法

127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> SADD set1  v1 v2 v3 v4 v5 v6
(integer) 6
127.0.0.1:6379> SADD set2 v2 v4 v6 v8 
(integer) 4
127.0.0.1:6379> SDIFFSTORE diff_set set1 set2 
(integer) 3
127.0.0.1:6379> SMEMBERS diff_set
1) "v5"
2) "v1"
3) "v3"
127.0.0.1:6379> SUNIONSTORE union_set  set1 set2
(integer) 7
127.0.0.1:6379> SMEMBERS union_set
1) "v4"
2) "v8"
3) "v5"
4) "v1"
5) "v6"
6) "v3"
7) "v2"
127.0.0.1:6379> SINTERSTORE inter_set set1 set2
(integer) 3
127.0.0.1:6379> SMEMBERS inter_set
1) "v6"
2) "v2"
3) "v4"
127.0.0.1:6379> 


上述命令主要是求差集、并集和交集 , 并保存到新的集合中。


Spring中操作Redis 集合命令

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context.xsd">
    <context:property-placeholder location="classpath:redis/redis.properties" />
    <!--2,注意新版本2.3以后,JedisPoolConfig的property name,不是maxActive而是maxTotal,而且没有maxWait属性,建议看一下Jedis源码或百度。 -->
    <!-- redis连接池配置 -->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!--最大空闲数 -->
        <property name="maxIdle" value="${redis.maxIdle}" />
        <!--连接池的最大数据库连接数 -->
        <property name="maxTotal" value="${redis.maxTotal}" />
        <!--最大建立连接等待时间 -->
        <property name="maxWaitMillis" value="${redis.maxWaitMillis}" />
        <!--逐出连接的最小空闲时间 默认1800000毫秒(30分钟) -->
        <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}" />
        <!--每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3 -->
        <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}" />
        <!--逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 -->
        <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}" />
      <property name="testOnBorrow" value="true"></property>
    <property name="testOnReturn" value="true"></property>
    <property name="testWhileIdle" value="true"></property>
    </bean>
  <!--redis连接工厂 -->
    <bean id="jedisConnectionFactory"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
        destroy-method="destroy">
        <property name="poolConfig" ref="jedisPoolConfig"></property>
        <!--IP地址 -->
        <property name="hostName" value="${redis.host.ip}"></property>
        <!--端口号 -->
        <property name="port" value="${redis.port}"></property>
        <!--如果Redis设置有密码 -->
        <property name="password" value="${redis.password}" /> 
        <!--客户端超时时间单位是毫秒 -->
        <property name="timeout" value="${redis.timeout}"></property>
        <property name="usePool" value="true" />
        <!--<property name="database" value="0" /> -->
    </bean>
  <!-- 键值序列化器设置为String 类型 -->
  <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
  <!-- redis template definition -->
  <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
    p:connection-factory-ref="jedisConnectionFactory"
    p:keySerializer-ref="stringRedisSerializer"
    p:defaultSerializer-ref="stringRedisSerializer"
    p:valueSerializer-ref="stringRedisSerializer">
  </bean>
</beans>
package com.artisan.redis.baseStructure.set;
import java.util.List;
import java.util.Set;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
/**
 * 
 * 
 * @ClassName: SpringRedisSetDemo
 * 
 * @Description: 记得将 RedisTemplate 值序列化器设置为 StringRedisSerializer 然后运行该代码
 * 
 * @author: Mr.Yang
 * 
 * @date: 2018年9月26日 下午3:22:57
 */
public class SpringRedisSetDemo {
  private static final String SET1 = "set1";
  private static final String SET2 = "set2";
  public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring/spring-redis-set.xml");
    RedisTemplate<String, String> redisTemplate = (RedisTemplate<String, String>) ctx.getBean("redisTemplate");
    Set<String> set = null;
    // 127.0.0.1:6379> FLUSHDB
    // OK
    // 127.0.0.1:6379> SADD set1 v1 v2 v3 v4 v5 v6
    // (integer) 6
    // 127.0.0.1:6379> SADD set2 v0 v2 v4 v6 v8
    // (integer) 5
    // 将元素加入列表
    redisTemplate.boundSetOps(SET1).add("v1", "v2", "v3", "v4", "v5", "v6");
    redisTemplate.boundSetOps(SET2).add("v0", "v2", "v4", "v6", "v8");
    // 127.0.0.1:6379> SCARD set1
    // (integer) 6
    // 求集合长度
    System.out.println(SET1 + "的长度为:" + redisTemplate.opsForSet().size(SET1));
    System.out.println(SET2 + "的长度为:" + redisTemplate.opsForSet().size(SET2));
    // 127.0.0.1:6379> SDIFF set1 set2
    // 1) "v5"
    // 2) "v1"
    // 3) "v3"
    // 求差集
    set = redisTemplate.opsForSet().difference(SET1, SET2);
    scanSet(set);
    // 求并集
    set = redisTemplate.opsForSet().intersect(SET1, SET2);
    scanSet(set);
    // 判断是否是集合中的元素
    boolean exist = redisTemplate.opsForSet().isMember(SET1, "v1");
    System.out.println(SET1 + "中存在v1:" + exist);
    // 获取集合所有元素
    set = redisTemplate.opsForSet().members(SET1);
    scanSet(set);
    set = redisTemplate.opsForSet().members(SET2);
    scanSet(set);
    // 从集合中随机弹出一个元素,集合中会删除该元素
    String randomValue = redisTemplate.opsForSet().pop(SET2);
    System.out.println(SET2 + "中弹出的随机元素为" + randomValue);
    System.out.println(SET2 + "的长度为:" + redisTemplate.opsForSet().size(SET2));
    // 随机获取一个集合的元素 ,该元素仍然在集合中
    randomValue = (String) redisTemplate.opsForSet().randomMember(SET1);
    System.out.println(randomValue);
    System.out.println(SET1 + "的长度为:" + redisTemplate.opsForSet().size(SET1));
    System.out.println("------------");
    // 随机获取集合中 的4 个元素
    List<String> list = redisTemplate.opsForSet().randomMembers(SET1, 4);
    for (String string : list) {
      System.out.println(string);
    }
    // 删除一个集合的元素,参数可以是多个
    Long value = redisTemplate.opsForSet().remove(SET1, "v1", "v2");
    System.out.println(SET1 + "中删除了" + value + "个元素");
    System.out.println(SET1 + "的长度为:" + redisTemplate.opsForSet().size(SET1));
    // 求两个集合的并集
    set = redisTemplate.opsForSet().union(SET1, SET2);
    scanSet(set);
    // 求两个集合的差集,并保存到集合 diff_set 中
    redisTemplate.opsForSet().differenceAndStore(SET1, SET2, "diff_set");
    scanSet(redisTemplate.opsForSet().members("diff_set"));
    // 求两个集合的交集,并保存到集合 inter_set 中
    redisTemplate.opsForSet().intersectAndStore(SET1, SET2, "inter_set");
    scanSet(redisTemplate.opsForSet().members("inter_set"));
    // 求两个集合的并集,并保存到集合 union_set 中
    redisTemplate.opsForSet().unionAndStore(SET1, SET2, "union_set");
    scanSet(redisTemplate.opsForSet().members("union_set"));
  }
  private static void scanSet(Set<String> set) {
    for (String value : set) {
      System.out.println(value);
    }
    System.out.println("----------------");
  }
}


输出

INFO : org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@73a8dfcc: startup date [Wed Sep 26 16:18:55 CST 2018]; root of context hierarchy
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [spring/spring-redis-set.xml]
set1的长度为:6
set2的长度为:5
v5
v1
v3
----------------
v4
v6
v2
----------------
set1中存在v1:true
v4
v5
v1
v6
v2
v3
----------------
v4
v8
v6
v2
v0
----------------
set2中弹出的随机元素为v4
set2的长度为:4
v3
set1的长度为:6
------------
v1
v1
v4
v4
set1中删除了2个元素
set1的长度为:4
v4
v5
v8
v6
v0
v3
v2
----------------
v5
v3
v4
----------------
v6
----------------
v4
v5
v8
v6
v0
v3
v2
----------------



注意


使用 Spring 提供的 RedisTemplate 去展示多个命令可以学习到如何使用 RedisTemplate 操作 Redis 。 实际工作中并不是那么用的,因为每一 个操作会尝试从连接池里获取 一 个新的 Redis 连接,多个命令应该使用SessionCallback 接口进行操作 。


代码

代码托管到了 https://github.com/yangshangwei/redis_learn

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
19天前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
19天前
|
开发者
除了交集运算,Set 类型还可以用于哪些数据结构的操作?
【10月更文挑战第30天】`Set`类型在数据结构操作方面提供了丰富的功能和便利,能够帮助开发者更高效地处理各种数据集合相关的任务,提高代码的简洁性和性能。
|
18天前
|
存储 NoSQL PHP
如何用Redis高效实现点赞功能?用Set?还是Bitmap?
在众多软件应用中,点赞功能几乎成为标配。本文从实际需求出发,探讨如何利用 Redis 的 `Set` 和 `Bitmap` 数据结构设计高效点赞系统,分析其优缺点,并提供 PHP 实现示例。通过对比两种方案,帮助开发者选择最适合的存储方式。
28 3
|
19天前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
19天前
|
Java 开发者
|
26天前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
30天前
|
存储 NoSQL Java
介绍下Redis 的基础数据结构
本文介绍了Redis的基础数据结构,包括动态字符串(SDS)、链表和字典。SDS是Redis自实现的动态字符串,避免了C语言字符串的不足;链表实现了双向链表,提供了高效的操作;字典则类似于Java的HashMap,采用数组加链表的方式存储数据,并支持渐进式rehash,确保高并发下的性能。
介绍下Redis 的基础数据结构
|
26天前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
26天前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
1月前
|
存储 消息中间件 NoSQL
Redis 数据结构与对象
【10月更文挑战第15天】在实际应用中,需要根据具体的业务需求和数据特点来选择合适的数据结构,并合理地设计数据模型,以充分发挥 Redis 的优势。
55 8
下一篇
无影云桌面