互联网应用主流框架整合【Redis数据结构及常用命令】

在大部分情况下我们使用Redis只是执行一些简单的命令操作,通常无需区分是否是在一个连接池里的同一个链接去执行,如果需要执行多条命令,需要保证命令在同一个链接里完成,则采用SessionCallback接口操作即可

Redis数据结构-字符串

字符串是Redis最基本的数据结构,它以一个键值对的形式存储在Redis中,犹如Java里的Map结构,字符串的基本命令如下表所示

在命令行终端执行上述命令,如下所示

bash 复制代码
> flushdb
"OK"

> set key01 value01
"OK"

> set key02 value02
"OK"

> get key01
"value01"

> get key02
"value02"

> del key01
(integer) 1

> get key01
(nil)

> strlen key02
(integer) 7

> getset key02 newvalue02
"value02"

> get key02
"newvalue02"

> getrange key02 0 5
"newval"

> append key02 _app
(integer) 14

> get key02
"newvalue02_app"
> 

如果是在Linux命令行下,如下所示

java 复制代码
	/**
	 * 测试Redis操作的静态方法
	 * 该方法通过Spring的ApplicationContext来管理Redis的配置和Bean
	 * 主要演示了Redis中String类型数据的各种操作,包括设置值、获取值、删除键、获取数据长度等
	 */
	public static void testString() {
		// 初始化Spring应用上下文,加载Redis配置
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(RedisConfig.class);
		// 从应用上下文中获取StringRedisTemplate Bean,用于操作Redis
		StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
		// 设值
		redisTemplate.opsForValue().set("key1", "value1");
		redisTemplate.opsForValue().set("key2", "value2");
		// 通过key获取值
		String value1 = redisTemplate.opsForValue().get("key1");
		// 输出获取的值
		System.out.println(value1);
		// 通过key删除值
		boolean success = redisTemplate.delete("key1");
		// 输出删除操作是否成功
		System.out.println(success);
		// 求长度
		Long length = redisTemplate.opsForValue().size("key2");
		// 输出长度
		System.out.println(length);
		// 设值新值并返回旧值
		String oldValue2 = redisTemplate.opsForValue().getAndSet("key2", "new_value2");
		// 输出旧值
		System.out.println(oldValue2);
		// 通过key获取值
		String value2 = redisTemplate.opsForValue().get("key2");
		// 输出获取的值
		System.out.println(value2);
		// 求子串
		String rangeValue2 = redisTemplate.opsForValue().get("key2", 0, 5);
		// 输出子串
		System.out.println(rangeValue2);
		// 追加字符串到末尾,返回新串长度
		int newLen = redisTemplate.opsForValue().append("key2", "_app");
		// 输出新的长度
		System.out.println(newLen);
		// 再次获取并输出更新后的值
		String appendValue2 = redisTemplate.opsForValue().get("key2");
		System.out.println(appendValue2);
		// 关闭应用上下文,释放资源
		applicationContext.close();
	}

在Spring中,redisTemplate.opsForValue()返回的对象可以操作简单的键值对,可以是字符串也可以是对象,具体要看所配置的序列化方案

此外,Redis的字符串类型不仅可以存储文本,也可以存储整数或浮点数,Redis还提供了整数和浮点数的支持,如果字符串是数字(整数或者浮点数),redis还支持简单的运算,但运算能力很弱,跟SQL的计算完全不是一回事

bash 复制代码
> set value 8
"OK"

> incr value
(integer) 9

> decr value
(integer) 8

> decrby value 2
(integer) 6

> incrbyfloat value 2.3
"8.300000000000001"
> 

因为Redis的计算能力比较差,通常只会用它来存储,而计算的部分交给Java来完成,如下代码所示

java 复制代码
    /**
     * 测试Redis操作的函数,主要进行数值计算相关的操作
     */
    public static void testCalculation() {
        // 创建IoC容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(RedisConfig.class);
        // 获取StringRedisTemplate
        StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
        // 设值
        redisTemplate.opsForValue().set("val", "8");
        printCurrValue(redisTemplate, "val");
        // 值加1
        redisTemplate.opsForValue().increment("val", 1);
        printCurrValue(redisTemplate, "val");
        // 获取Redis底层连接
        RedisConnection conn = redisTemplate.getConnectionFactory().getConnection();
        // 值减1
        conn.decr("val".getBytes());
        printCurrValue(redisTemplate, "val");
        // 值减2
        conn.decrBy("val".getBytes(), 2);
        conn.close(); // 关闭连接
        printCurrValue(redisTemplate, "val");
        // 加浮点数
        redisTemplate.opsForValue().increment("val", 2.3);
        printCurrValue(redisTemplate, "val");
    }

    public static void printCurrValue(StringRedisTemplate redisTemplate, String key) {
        String i = (String) redisTemplate.opsForValue().get(key);
        System.out.println(i);
    }

这里使用的是StringRedisTemplate,所以Redis保存的还是字符串,如果采用其他的序列化器,比如JDK序列化器,Redis将不会保存数字,而是产生异常;Spring优化了代码所以increment方法可以支持整型(long)和双精度(double)的加法,对于减法RedisTemplate并没有给予支持,所以用了其他的方法实现减法

java 复制代码
        // 获取Redis底层连接
        RedisConnection conn = redisTemplate.getConnectionFactory().getConnection();
        // 值减1
        conn.decr("val".getBytes());

通过获得连接工厂再获得链接,得到底层链接(RedisConnection)对象,为了和StringRedisTemplate的配置保持一致,将参数转化为字符串,getConnection()可以获取一个spring-data-redis项目中封装的底层对象RedisConnection,甚至可以获取原始的链接对象Jedis对象

java 复制代码
Jedis jedis = (Jedis)redisTemplate.getConnectionFactory().getConnection().getNativeConnection();

需要注意的是,RedisTemplate支持的命令会和底层连接有一定的差异,且有些功能并不齐全,如果使用纯粹的Redis Java API则可以看到命令对应的方法,所有关于减法的方法,原有值都必须是整数,否则会引发异常

java 复制代码
redisTemplate.opsForValue().set("i", "8.9");
redisTemplate.getConnectionFactory().getConnection().decr("val".getBytes());

浮点数减法会产生异常,但在编译时候可以通过

Redis数据结构-哈希

Redis中的哈希(Hash)数据结构同Java中的哈希(HashMap)数据结构一样,一个对象里边有很多键值对,特别适合存储对象,如果内存足够大,甚至能够存储40多亿个键值对数据,通常不会有这么多键值对数据,所以甚至可以理解为哈希数据的存储是无限的。

在Redis中哈希数据结构是一个String类型的field和value的映射表,因此存储的数据在Redis内存中实际上都是一个个字符串,例如一个角色对象用哈希结构存储,在内存中如下表所示

role_1就是这个哈希结构的key,通过它可以找到这个哈希结构的数据,而哈希结构由一系列的field和value组成,通过Redis命令操作,如下所示

bash 复制代码
> HMSET role_1 id 001 role_name role_name_001 note note_001
"OK"

> HGETALL role_1
1) "id"
2) "001"
3) "role_name"
4) "role_name_001"
5) "note"
6) "note_001"

> HSET role_1 age 25
(integer) 1

> HGETALL role_1
1) "id"
2) "001"
3) "role_name"
4) "role_name_001"
5) "note"
6) "note_001"
7) "age"
8) "25"

注意多个字段和单个字段的命令不同
在Redis中角色对象通过键role_1索引的,而角色本身是一个哈希数据结构,哈希数据结构的键值在内存中是一种无序状态,需要通过键来找到对应的值

Redis中哈希数据结构命令如下表所示

在使用哈希数据结构命令时,需要注意哈希数据结构的大小,尤其那些需要返回整个哈希数据结构的命令,会造成大量的读取,这种时候要考虑性能和对内存的影响;对于数字的操作命令hincrby要求存储的是整数型字符串;hincrbyfloat则要求是浮点数或者整数型,否则命令会失败

bash 复制代码
> HMSET hash f1 value1 f2 value2
"OK"

> HSET hash f3 value3
(integer) 1

> HEXISTS hash f2
(integer) 1

> HGETALL hash
1) "f1"
2) "value1"
3) "f2"
4) "value2"
5) "f3"
6) "value3"

> HSET hash f4 4
(integer) 1

> HINCRBY hash f4 8
(integer) 12

> HINCRBYFLOAT hash f4 0.44
"12.44"

> HKEYS hash
1) "f1"
2) "f2"
3) "f3"
4) "f4"

> HLEN hash
(integer) 4

> HMGET hash f1 f2
1) "value1"
2) "value2"

> HSETNX hash f3 newvalue3
(integer) 0

> HSETNX hash f4 newvalue3
(integer) 0

> HSETNX hash f5 newvalue3
(integer) 1

> HVALS hash
1) "value1"
2) "value2"
3) "value3"
4) "12.44"
5) "newvalue3"

> HDEL hash f1
(integer) 1
> 

在Spring中完成相同的Redis数据操作,代码如下:

java 复制代码
    public static void testHash() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(RedisConfig.class);
        StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
        String key = "hash";
        Map<String, String> map = new HashMap<String, String>();
        map.put("f1", "val1");
        map.put("f2", "val2");
        // 相当于hmset命令
        redisTemplate.opsForHash().putAll(key, map);
        // 相当于hset命令
        redisTemplate.opsForHash().put(key, "f3", "6");
        printValueForhash(redisTemplate, key, "f3");
        // 相当于 hexists key filed命令
        boolean exists = redisTemplate.opsForHash().hasKey(key, "f3");
        System.out.println(exists);
        // 相当于hgetall命令
        Map keyValMap = redisTemplate.opsForHash().entries(key);
        // 相当于hincrby命令
        redisTemplate.opsForHash().increment(key, "f3", 2);
        printValueForhash(redisTemplate, key, "f3");
        // 相当于hincrbyfloat命令
        redisTemplate.opsForHash().increment(key, "f3", 0.88);
        printValueForhash(redisTemplate, key, "f3");
        // 相当于hvals命令
        List valueList = redisTemplate.opsForHash().values(key);
        // 相当于hkeys命令
        Set keyList = redisTemplate.opsForHash().keys(key);
        List<String> fieldList = new ArrayList<String>();
        fieldList.add("f1");
        fieldList.add("f2");
        // 相当于hmget命令
        List valueList2 = redisTemplate.opsForHash().multiGet(key, keyList);
        // 相当于hsetnx命令
        boolean success = redisTemplate.opsForHash().putIfAbsent(key, "f4", "val4");
        System.out.println(success);
        // 相当于hdel命令
        Long result = redisTemplate.opsForHash().delete(key, "f1", "f2");
        System.out.println(result);
    }

    private static void printValueForhash(StringRedisTemplate redisTemplate, String key, String field) {
        // 相当于hget命令
        Object value = redisTemplate.opsForHash().get(key, field);
        System.out.println(value);
    }

需要注意的是:

  • HMSET命令在Java的API中,是使用Map保存多个键值对在先的
  • HGETALL命令返回所有的键值对,并保存到一个Map对象中,如果哈希结构过大,必须考虑对JVM的影响
  • HINCRBY和HINCRBYFLOAT命令都采用increment方法,Spring会识别它们具体使用何种Redis命令,无需去了解底层
  • redisTemplate.opsForHash().values(key)方法相当于HVALS命令,它会返回所有的值,并保存到一个List对象中,而redisTemplate.opsForHash().keys()方法相当于HKEYS命令,它会获得所有的键并保存到一个Set对象中
  • redisTemplate.opsForHash().putAll(key, map)方法相当于HMSET命令,.由于StringRedisTemplate的序列化器为字符串类型,所有它只会用字符串转化,这样才能执行对应的数值加,如果使用其他的序列化器,则相关命令可能会报异常
  • 在使用大的哈希数据结构时,需要考虑返回数据的大小,避免内存溢出和Redis性能下降

Redis数据结构-链表

链表(linked-list)结构是Redis中一个常用的结构,它可以存储多个字符串,而且是有序的,可以从左到右,也可以从右到左双向遍历其存储节点,双向链表只能从左到右或者从右到左的访问和操作链表内的数据节点,因此这就意味着读性能的丧失,链表的优势在于插入和删除的便利,因为链表的数据节点被分配在不同的内存区域并不连续,只是根据上一个节点指向下一个节点的顺序来索引,无需移动节点

因为是双向链表,所以Redis的链表命令分为左操作和右操作,左操作是从左到右,右操作是从右到左,如下表所示

bash 复制代码
> lpush list node3 node2 node1
(integer) 3

> rpush list node4
(integer) 4

> lindex list 0
"node1"

> llen list 
(integer) 4

> lpop list
"node1"

> rpop list
"node4"

> linsert list before node2 before_node
(integer) 3

> linsert list after node2 after_node
(integer) 4

> lpushx list head
(integer) 5

> rpushx list end
(integer) 6

> lrange list 0 10
1) "head"
2) "before_node"
3) "node2"
4) "after_node"
5) "node3"
6) "end"

> lpush list node node node 
(integer) 9

> lrem list 3 node
(integer) 3

> lset list 0 new_head_value
"OK"

> lrange list 0 10
1) "new_head_value"
2) "before_node"
3) "node2"
4) "after_node"
5) "node3"
6) "end"

> ltrim list 0 2
"OK"

> lrange list 0 10
1) "new_head_value"
2) "before_node"
3) "node2"
> 

使用StringRedisTemplate测试代码如下

java 复制代码
    public static void testList() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(RedisConfig.class);
        StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
        try {
            // 删除链表,以便我们可以反复测试
            redisTemplate.delete("list");
            List<String> nodeList = new ArrayList<String>();
            for (int i = 3; i >= 1; i--) {
                nodeList.add("node" + i);
            }
            // 相当于lpush把多个价值从左插入链表
            redisTemplate.opsForList().leftPushAll("list", nodeList);
            // 从右边插入一个节点
            redisTemplate.opsForList().rightPush("list", "node4");
            // 获取下标为0的节点
            String node1 = redisTemplate.opsForList().index("list", 0);
            // 获取链表长度
            long size = redisTemplate.opsForList().size("list");
            // 从左边弹出一个节点
            String lpop = redisTemplate.opsForList().leftPop("list");
            // 从右边弹出一个节点
            String rpop = redisTemplate.opsForList().rightPop("list");
            // 注意,需要使用更为底层的命令才能操作linsert命令
            // 使用linsert命令在node2前插入一个节点
            redisTemplate.getConnectionFactory().getConnection().lInsert("list".getBytes("utf-8"),
                    RedisListCommands.Position.BEFORE, "node2".getBytes("utf-8"), "before_node".getBytes("utf-8"));
            // 使用linsert命令在node2后插入一个节点
            redisTemplate.getConnectionFactory().getConnection().lInsert("list".getBytes("utf-8"),
                    RedisListCommands.Position.AFTER, "node2".getBytes("utf-8"), "after_node".getBytes("utf-8"));
            // 判断list是否存在,如果存在则从左边插入head节点
            redisTemplate.opsForList().leftPushIfPresent("list", "head");
            // 判断list是否存在,如果存在则从右边插入end节点
            redisTemplate.opsForList().rightPushIfPresent("list", "end");
            // 从左到右,或者下标从0到10的节点元素
            List<String> valueList = redisTemplate.opsForList().range("list", 0, 10);
            System.out.println(valueList);
            // 清空原有节点
            nodeList.clear();
            for (int i = 1; i <= 3; i++) {
                nodeList.add("node");
            }
            // 在链表左边插入三个值为node的节点
            redisTemplate.opsForList().leftPushAll("list", nodeList);
            // 从左到右删除至多三个node节点
            redisTemplate.opsForList().remove("list", 3, "node");
            // 给链表下标为0的节点设置新值
            redisTemplate.opsForList().set("list", 0, "new_head_value");
            // 打印链表数据
            printList(redisTemplate, "list");
            // 相当于ltrim命令
            redisTemplate.opsForList().trim("list", 0, 2);
        } catch (UnsupportedEncodingException ex) {
            ex.printStackTrace();
        } finally {
            // 打印链表数据
            printList(redisTemplate, "list");
            applicationContext.close();
        }
    }

    public static void printList(StringRedisTemplate redisTemplate, String key) {
        // 链表长度
        Long size = redisTemplate.opsForList().size(key);
        // 获取整个链表的值
        List<String> valueList = redisTemplate.opsForList().range(key, 0, size);
        // 打印
        System.out.println(valueList);
    }

需要指出的是,这些操作是进程不安全的,也就是当操作这些命令的时候,其他Redis的客户端也可能在操作同一个链表,这样就会引发数据一致性问题,为此Redis给出了链表的阻塞命令,在运行此类命令的时候会给链表加锁,从而保证链表的命令安全性,相关命令如下表所示

在使用这些命令的时候,Redis就会对对应的链表加锁,加锁之后其他客户端就不能再写入和读取该链表,只能等待命令结束,加锁的好吃就是更好地保证了数据一致性,对于重要数据非常有用,但要付出的代价就是其他线程的等待、线程环境切换等,最终使系统的并发能力下降,在实际的使用中,虽然阻塞命令可以保证数据的一致性,但这意味着其他进程的等待,CPU需要对其他线程进行挂起、恢复等操作,因此这些指令在实际项目中使用率并不高

bash 复制代码
> lpush listblock node1 node2 node3 node4 node5
(integer) 5

> blpop listblock 2
1) "listblock"
2) "node5"

> blpop listblock 3
1) "listblock"
2) "node4"

> brpop listblock 3
1) "listblock"
2) "node1"

> lpush listblock2 data1 data2 data3
(integer) 3

> rpoplpush listblock listblock2
"node2"

> brpoplpush listblock listblock2 3
"node3"

> lrange listblock 0 10
(empty list or set)

> lrange listblock2 0 10
1) "node3"
2) "node2"
3) "data3"
4) "data2"
5) "data1"
> 

Spring对Redis阻塞命令的使用,代码如下

java 复制代码
    public static void testBlockList() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(RedisConfig.class);
        StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
        try {
            // 清空数据,可以重复测试
            redisTemplate.delete("list1");
            redisTemplate.delete("list2");
            // 初始化链表list1
            List<String> nodeList = new ArrayList<String>();
            for (int i = 1; i <= 5; i++) {
                nodeList.add("node" + i);
            }
            redisTemplate.opsForList().leftPushAll("list1", nodeList);
            // Spring使用参数超时时间作为阻塞命令区分,等价于blpop命令,并且可以设置时间参数
            redisTemplate.opsForList().leftPop("list1", 2, TimeUnit.SECONDS);
            // Spring使用参数超时时间作为阻塞命令区分,等价于brpop命令,并且可以设置时间参数
            redisTemplate.opsForList().rightPop("list1", 3, TimeUnit.SECONDS);
            nodeList.clear();
            // 初始化链表list2
            for (int i = 1; i <= 3; i++) {
                nodeList.add("data" + i);
            }
            redisTemplate.opsForList().leftPushAll("list2", nodeList);
            // 相当于rpoplpush命令,弹出list1最右边的节点,插入到list2最左边
            redisTemplate.opsForList().rightPopAndLeftPush("list1", "list2");
            // 相当于brpoplpush命令,注意在Spring中使用超时参数区分
            redisTemplate.opsForList().rightPopAndLeftPush("list1", "list2", 1, TimeUnit.SECONDS);
        } finally {
            // 打印链表数据
            printList(redisTemplate, "list1");
            printList(redisTemplate, "list2");
            applicationContext.close();
        }
    }

Redis数据结构-集合

Redis集合不是一个线性结构,而是一个哈希表结构,其内部是根据哈希因子存储和查找数据,理论上一个集合可以存储42亿个成员,因为采用哈希结构,所以对于Redis集合的插入、删除和查找的复杂度都是O(1),但需要注意对于每个集合而言,它的每个成员不能重复,并且集合是无序的集合中的每个成员都是Strings数据结构类型

Redis还可以对不同集合进行操作,例如求集合的交集、差集和并集等,命令如下表所示

bash 复制代码
> sadd set1 v1 v2 v3 v4 v5 v6 
(integer) 6

> sadd set2 v2 v4 v6 v8 v10
(integer) 5

> scard v1
(integer) 0

> scard set1
(integer) 6

> sdiff set1 set2
1) "v3"
2) "v1"
3) "v5"

> sinter set1 set2
1) "v2"
2) "v6"
3) "v4"

> sismember set2 v1
(integer) 0

> sismember set1 v1
(integer) 1

> smembers set2
1) "v2"
2) "v6"
3) "v4"
4) "v10"
5) "v8"

> spop set1
"v4"

> smembers set1
1) "v1"
2) "v5"
3) "v2"
4) "v6"
5) "v3"

> srandmember set1 2
1) "v2"
2) "v3"

> srem set1 v1
(integer) 1

> smembers set1
1) "v5"
2) "v2"
3) "v6"
4) "v3"

> sunion set1 set2
1) "v5"
2) "v2"
3) "v6"
4) "v3"
5) "v10"
6) "v4"
7) "v8"
> 

交集、并集、差集命令用法

bash 复制代码
> sadd set3 v1 v2 v3 v4 v5 v6
(integer) 6

> sadd set4 v2 v4 v6 v8
(integer) 4

> sdiffstore diff_set set3 set4
(integer) 3

> smembers diff_set
1) "v3"
2) "v1"
3) "v5"

> sunionstore union_set set3 set4
(integer) 7

> smembers union_set
1) "v1"
2) "v5"
3) "v2"
4) "v3"
5) "v6"
6) "v4"
7) "v8"

> sinterstore inter_set set3 set4
(integer) 3

> smembers inter_set
1) "v6"
2) "v4"
3) "v2"

在Spring中对集合的使用,代码如下

java 复制代码
    public static void testSet() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(
                RedisConfig.class);
        StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
        Set<String> set = null;
        // 将成员加入列表
        redisTemplate.boundSetOps("set1").add("v1", "v2", "v3", "v4", "v5", "v6");
        redisTemplate.boundSetOps("set2").add("v2", "v4", "v6", "v8", "v10");
        // 求集合长度
        redisTemplate.opsForSet().size("set1");
        // 求差集
        set = redisTemplate.opsForSet().difference("set1", "set2");
        // 求并集
        set = redisTemplate.opsForSet().intersect("set1", "set2");
        // 判断是否集合中的成员
        boolean exists = redisTemplate.opsForSet().isMember("set1", "v1");
        // 获取集合所有成员
        set = redisTemplate.opsForSet().members("set1");
        System.out.println(set);
        // 从集合中随机弹出一个成员
        String val = redisTemplate.opsForSet().pop("set1");
        // 随机获取一个集合的成员
        val = redisTemplate.opsForSet().randomMember("set1");
        // 随机获取2个集合的成员
        List<String> list = redisTemplate.opsForSet().randomMembers("set1", 2L);
        // 删除一个集合的成员,参数可以是多个
        redisTemplate.opsForSet().remove("set1", "v1");
        // 求两个集合的并集
        redisTemplate.opsForSet().union("set1", "set2");
        /**************** 集合运算 ****************/
        // 清空2个集合
        redisTemplate.delete("set1");
        redisTemplate.delete("set2");
        // 将成员加入列表
        redisTemplate.boundSetOps("set1").add("v1", "v2", "v3", "v4", "v5", "v6");
        redisTemplate.boundSetOps("set2").add("v2", "v4", "v6", "v8");
        // 求两个集合的差集,并保存到集合diff_set中
        redisTemplate.opsForSet().differenceAndStore("set1", "set2", "diff_set");
        printSet(redisTemplate, "diff_set");
        // 求两个集合的交集,并保存到集合inter_set中
        redisTemplate.opsForSet().intersectAndStore("set1", "set2", "inter_set");
        printSet(redisTemplate, "inter_set");
        // 求两个集合的并集,并保存到集合union_set中
        redisTemplate.opsForSet().unionAndStore("set1", "set2", "union_set");
        printSet(redisTemplate, "union_set");
    }

    private static void printSet(StringRedisTemplate redisTemplate, String key) {
        Set<String> set = redisTemplate.opsForSet().members(key);
        System.out.println(set);
    }

Redis数据结构-有序集合

有序集合和集合类似,只不过它是有序的,它和无序集合的主要区别在于每个成员除了值,还会多一个分数(score),分数是一个浮点数,在Java中使用双精度表示,根据分数,Redis可以支持对分数从小到大和从大到小的排序;和无序集合一样,每个成员都是唯一的,但对于不同的成员他们的分数可以是一样的,成员也是String数据结构,也是一种基于Hash的存储结构,且有序集合和无需集合一样是通过哈希表实现的,所以添加、删除、查找的复杂度都是O(1),有序集合的数据结构如下图所示

它依赖key标示它属于哪个集合,依赖分数排序,所以值和分数是必须的,且在满足一定的条件下,也可以对值进行排序

常用命令如下表所示:

bash 复制代码
> zadd zset1 1 x1 2 x2 3 x3 4 x4 5 x5 6 x6 7 x7 8 x8 9 x9
(integer) 9

> zadd zset2 1 y1 2 y2 3 y3 4 y4 5 y5 6 y6 7 y7 8 y8 9 y9
(integer) 9

> zcard zset1
(integer) 9

> zcount zset1 1 4
(integer) 4

> zinterstore inter_zset 2 zset1 zset2
(integer) 0

> zlexcount zset1 (x1 [x5
(integer) 4

> zrange zset1 1 5 withstores
"ERR syntax error"

> zrange zset1 1 5 Withstores
"ERR syntax error"

> zrange zset1 1 5 WITHSCORES
1) "x2"
2) "2"
3) "x3"
4) "3"
5) "x4"
6) "4"
7) "x5"
8) "5"
9) "x6"
10) "6"

> zrange zset1 5 7 WITHSCORES
1) "x6"
2) "6"
3) "x7"
4) "7"
5) "x8"
6) "8"

> zrank zset1 x5
(integer) 4

> zrangebylex zset1 (x1 [x6
1) "x2"
2) "x3"
3) "x4"
4) "x5"
5) "x6"

> zrangebyscore zset1 2 7 WITHSCORES limit 1 5
1) "x3"
2) "3"
3) "x4"
4) "4"
5) "x5"
6) "5"
7) "x6"
8) "6"
9) "x7"
10) "7"

> zrevrange zset1 1 5
1) "x8"
2) "x7"
3) "x6"
4) "x5"
5) "x4"

> zadd zset3 1 a1 2 a2 3 a3 4 a4 5 a5 6 a6 7 a7 8 a8 9 a9
(integer) 9

> zadd zset4 1 b1 2 c2 3 b3 4 c4 5 b5 6 c6 7 b7 8 c8 9 b9
(integer) 9

> zrevrangebyscore zset4 5 2 WITHSCORES
1) "b5"
2) "5"
3) "c4"
4) "4"
5) "b3"
6) "3"
7) "c2"
8) "2"

> zrevrank zset3 x4
(nil)

> zrevrank zset3 a4
(integer) 5

> zscore zset3 a5
"5"

> zunionstore union 2 zset3 zset4
(integer) 18

> zincrby zset3 5 a9
"14"

> zremrangebyscore zset3 3 2
(integer) 0

> zremrangebyscore zset3 3 5
(integer) 3

> zremrangebyrank zset3 1 3
(integer) 3

> zremrangebylex zset4 [b1 [b5
(integer) 1

> 

在Spring中操作有序集合

java 复制代码
    public static void testZset() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(RedisConfig.class);
        StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
        // 清空有序集合
        clearZset(redisTemplate);
        // Spring提供接口TypedTuple操作有序集合
        Set<ZSetOperations.TypedTuple<String>> set1 = new HashSet<>();
        Set<ZSetOperations.TypedTuple<String>> set2 = new HashSet<>();
        int j = 9;
        for (int i = 1; i <= 9; i++) {
            j--;
            // 计算分数和值
            Double score1 = Double.valueOf(i);
            String value1 = "x" + i;
            Double score2 = Double.valueOf(j);
            String value2 = j % 2 == 1 ? "y" + j : "x" + j;
            // 使用Spring提供的DefaultTypedTuple创建成员
            ZSetOperations.TypedTuple<String> typedTuple1 = new DefaultTypedTuple<>(value1, score1);
            set1.add(typedTuple1);
            ZSetOperations.TypedTuple<String> typedTuple2 = new DefaultTypedTuple<>(value2, score2);
            set2.add(typedTuple2);
        }

        // 将成员插入有序集合
        redisTemplate.opsForZSet().add("zset1", set1);
        redisTemplate.opsForZSet().add("zset2", set2);
        // 统计总数
        Long size = null;
        size = redisTemplate.opsForZSet().zCard("zset1");
        // 计分数为score,那么下面的方法就是求1<=score<=4的成员
        size = redisTemplate.opsForZSet().count("zset1", 1, 4);
        // 将zset1和zset2两个集合的交集放入集合inter_zset
        size = redisTemplate.opsForZSet().intersectAndStore("zset1", "zset2", "inter_zset");
        Set<String> set = null;
        Set<ZSetOperations.TypedTuple<String>> setMember = null;
        // 区间
        RedisZSetCommands.Range range = RedisZSetCommands.Range.range();
        range.gte("x1"); // 大于等于
        range.lt("x5"); // 小于
        // 返回值为区间["x1", "x5")的所有成员
        set = redisTemplate.opsForZSet().rangeByLex("zset1", range);
        // 截取集合所有成员,并且对集合按分数排序,并返回分数,每一个成员是TypedTuple<String>
        setMember = redisTemplate.opsForZSet().rangeWithScores("zset1", 1, 5);
        printTypedTuple(setMember);
        setMember = redisTemplate.opsForZSet().rangeWithScores("zset1", 5, 7);
        printTypedTuple(setMember);
        // 从下标1开始截取下标到5的成员,但不返回分数,每一个成员是String
        set = redisTemplate.opsForZSet().range("zset1", 1, 5);
        printZSet(set);
        // 求排行,排名第1返回0,第2返回1,以此类推
        Long rank = redisTemplate.opsForZSet().rank("zset1", "x5");
        System.out.println("rank = " + rank);
        printZSet(set);
        range.gt("x1"); // 大于
        range.lte("x6"); // 小等于
        // 返回区间为(x1, x6]的成员
        set = redisTemplate.opsForZSet().rangeByLex("zset1", range);
        printZSet(set);
        // 求分数为区间(2, 7]内的成员,并限制返回至多5条
        set = redisTemplate.opsForZSet().rangeByScore("zset1", 2, 7, 1, 5);
        printZSet(set);
        // 从大到小排序的成员,然后返回下标为1到5的成员
        set = redisTemplate.opsForZSet().reverseRange("zset1", 1, 5);
        printZSet(set);

        // 清空有序集合
        clearZset(redisTemplate);
        // 将成员插入有序集合
        redisTemplate.opsForZSet().add("zset1", set1);
        redisTemplate.opsForZSet().add("zset2", set2);

        // 从大到小排序,并返回分数在区间[2.5]的成员,返回时带分数
        setMember = redisTemplate.opsForZSet().reverseRangeByScoreWithScores("zset2", 5, 2);
        printTypedTuple(setMember);
        // 求x4在有序集合zset1中从大到小的排行
        redisTemplate.opsForZSet().reverseRank("zset1", "x4");
        // 求x5在有序集合zset1的分数
        redisTemplate.opsForZSet().score("zset1", "x5");
        // 求两个有序集合的交集,并将它们保存到新的有序集合union中
        redisTemplate.opsForZSet().unionAndStore("zset1", "zset2", "union");
        // 给集合中的一个成员的分数加上5
        redisTemplate.opsForZSet().incrementScore("zset1", "x9", 5);
        // 根据分数区间[3, 2],删除有序集合中的成员,实际不删成员
        size = redisTemplate.opsForZSet().removeRangeByScore("zset1", 3, 2);
        System.out.println("delete1 = " + size);
        // 根据下标1到3删除成员
        size = redisTemplate.opsForZSet().removeRange("zset1", 1, 3);
        System.out.println("delete2 = " + size);
        // 根据成员值删除成员
        size = redisTemplate.opsForZSet().remove("zset1", "x5", "x6");
        System.out.println("delete3 = " + size);
        applicationContext.close();
    }

    // 清空有序集合
    private static void clearZset(StringRedisTemplate redisTemplate) {
        redisTemplate.delete("zset1");
        redisTemplate.delete("zset2");
    }

    /**
     * 打印TypedTuple集合
     *
     * @param set -- Set<TypedTuple>
     */
    private static void printTypedTuple(Set<ZSetOperations.TypedTuple<String>> set) {

        if (set != null && set.isEmpty()) {
            return;
        }
        Iterator<ZSetOperations.TypedTuple<String>> iterator = set.iterator();
        System.out.print("【");
        while (iterator.hasNext()) {
            ZSetOperations.TypedTuple<String> val = iterator.next();
            System.out.print("{value=" + val.getValue() + ", score =" + val.getScore() + "}\t");
        }
        System.out.println("】");
    }

    /**
     * 打印普通集合
     */
    private static void printZSet(Set set) {
        if (set != null && set.isEmpty()) {
            return;
        }
        Iterator iterator = set.iterator();
        System.out.print("【");
        while (iterator.hasNext()) {
            Object val = iterator.next();
            System.out.print(val + "\t");
        }
        System.out.println("】");
    }
相关推荐
Oak Zhang18 分钟前
sharding-jdbc自定义分片算法,表对应关系存储在mysql中,缓存到redis或者本地
redis·mysql·缓存
门牙咬脆骨1 小时前
【Redis】redis缓存击穿,缓存雪崩,缓存穿透
数据库·redis·缓存
门牙咬脆骨1 小时前
【Redis】GEO数据结构
数据库·redis·缓存
先鱼鲨生1 小时前
数据结构——栈、队列
数据结构
一念之坤1 小时前
零基础学Python之数据结构 -- 01篇
数据结构·python
IT 青年2 小时前
数据结构 (1)基本概念和术语
数据结构·算法
熬夜学编程的小王2 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
liujjjiyun2 小时前
小R的随机播放顺序
数据结构·c++·算法
墨鸦_Cormorant3 小时前
使用docker快速部署Nginx、Redis、MySQL、Tomcat以及制作镜像
redis·nginx·docker
Reese_Cool4 小时前
【数据结构与算法】排序
java·c语言·开发语言·数据结构·c++·算法·排序算法