Redis-07Redis数据结构--有序集合ZSet

概述

有序集合和集合类似,只是说它是有序的,和无序集合的主要区别在于每一个元素除了值之外,它还会多一个分数。

  1. 分数是一个浮点数,在 Java 中是使用双精度表示的,根据分数, Redis 就可以支持对分数从小到大或者从大到小的排序
  2. 和无序集合一样,对于每一个元素都是唯一的 ,但是对于不同元素而言,它的分数可以一样
  3. 元素也是 String 数据类型,也是一种基于 hash 的存储结构。
  4. 集合是通过哈希表实现的,所以添加、删除、 查找的复杂度都是 0(1)
  5. 集合中最大的成员数为 2的32次方减 1 ( 40 多亿个成员)

有序集合的数据结构

在这里插入图片描述

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


Redis 有序集合的部分命令

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

有序集合和无序集合的命令是接近的,只是在这些命令的基础上,会增加对于排序的操作,这些是我们在使用的时候需要注意的细节.

有些时候 Redis 借助数据区间的表示方法来表示包含或者不包含,比如在数学的区间表示中[2,5 ]表示包含 2,但是不包含 5 的 区间。

在这里插入图片描述


命令说明备注
zadd key score1 value1 [score2 value2 …]向有序集合的 key,增加一个或者多个成员如果不存在对应的 key,则创建键为 key 的有序集合
zcard key获取有序集合的成员数-----
zcount key min max根据分数返回对应的成员列表min 为最小值, max为最大值,默认为包含min 和 max 值,采用数学区间表示的方法,如果需要不包含,则在分数前面加入“(”,注意不支持“[”表示
zincrby key increment member给有序集合成员值为 member 的分数增加 increment-----
zinterstore desKey nurnkeys key1 [key2 key3 …]求多个有序集合的交集,并且将结果保存到 des Key 中numkeys 是一个整数,表示多少个有序集合
zlexcount key min max求有序集合 key 成员值在 min 和 max 的范围这里范围为 key 的成员值, Redis 借助数据区间的表示方法,“[”表示包含该值,“(”表示不包含该值
zrange key start stop [withscores]按照分值的大小〈从小到大)返回成员,加入 start 和 stop 参数可以截取某一段返回.如果输入可选项 withscores,则连同分数一起返回这里记集合最大长度为len,Redis 会将集合排序后,形成一个从 0 到len-1的下标,然后根据 start 和 stop 控制的下标(包含 start 和 stop)返回
zrank key member按从小到大求有序集合的排行排名第一的为 0,第二的为 1 …
zrangebylex key min max [limit offset count]根据值的大小,从小到大排序, min 为最小值, max 为最大值;limit 选项可选,当 Red is 求出范围集合后,会生产下标0到n,然后根据偏移量offset 和限定返回 数 count,返回对应的成员这里范围为 key 的成员值, Red i s 借助数学区间的表示方法,“[”表示包含该值,“(”表示不包含该值
zrangebyscore key min max [withscores] [limit offset count]根据分数大小,从小到大求取范围,选项 withscores 和 limit 请参考 zrange 命令和zrangebylex 说明根据分析求取集合的范围。这里默认包含 min和 max,如果不想包含,则在参数前加入“(”,注意不支持“ [”表示
zremrangebyscore key start stop根据分数区间进行删除按照 socre 进行排序,然后排除 0 到len-1的下标,然后根据 start 和 stop 进行删除, Redis 借助数学区间的表示方法,“[”表示包含该值,“(”表示不包含该值
zremrangebyrank key start stop按照分数排行从小到大的排序删除,从0开始计算-----
zremrangebylex key min max按照值的分布进行删除-----
zrevrange key start stop [withscores]从大到小的按分数排序,参数请参见zrange与 zrange 相同,只是排序是从大到小
zrevrangebyscore key max min [withscores]从大到小的按分数排序,参数请参见zrangebyscore与 zrangebyscore 相同 ,只是排序是从大到小
zrevrank key member按从大到小的顺序,求元素的排行排名第一位 0,第二位1 …
zscore key member返回成员的分数值返回成员的分数
zunionstore desKey numKeys key1 [key2 key3 key4 …]求多个有序集合的并集,其中 numKeys是有序,集合的个数-----

在对有序集合、下标、区间的表示方法进行操作的时候,需要十分小心命令,注意它是操作分数还是值,稍有不慎就会出现问题。

# 为了测试的数据干净,删除当前db的数据
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> 
127.0.0.1:6379> 
#  zadd key score1 value1 [score2 value2 …] 向有序集合zset1 ,增加9个成员 
127.0.0.1:6379> ZADD zset1 1 x1 2 x2 3 x3 4 x4 5 x5 6 x6 7 x7 8 x8 9 x9
(integer) 9


#  zadd key score1 value1 [score2 value2 …] 向有序集合zset2 ,增加9个成员
127.0.0.1:6379> ZADD zset2 1 y1 2 x2 3 y3 4 x4 5 y5 6 x6 7 y7 8 x8 9 y9
(integer) 9


# zcard key 获取有序集合zset1的成员数
127.0.0.1:6379> ZCARD zset1
(integer) 9


# zcount key min max  根据分数返回对应的成员列表
127.0.0.1:6379> ZCOUNT zset1 1 4 
(integer) 4

# zinterstore desKey nurnkeys key1 [key2 key3 …] 求多个有序集合的交集,并且将结果保存到 des Key 中
127.0.0.1:6379> ZINTERSTORE des_key 2 zset1 zset2 
(integer) 4

# zlexcount key min max  求有序集合 zset1 成员值在 min 和 max 的范围 [ 表示包含该值,( 表示不包含该值
127.0.0.1:6379> ZLEXCOUNT zset1 (x1 [x5
(integer) 4

# zrange key start stop [withscores] 按照分值的大小(从小到大)返回成员,加入 start 和 stop 参数可以截取某一段返回.如果输入可选项 withscores,则连同分数一起返回
127.0.0.1:6379> 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"



#  zrank key member 按从小到大求有序集合的排行
127.0.0.1:6379> ZRANK zset1 x5
(integer) 4

#  zrangebylex key min max [limit offset count]根据值的大小,从小到大排序   [表示包含该值 (表示不包含该值
127.0.0.1:6379> ZRANGEBYLEX zset1 (x1 [x6
1) "x2"
2) "x3"
3) "x4"
4) "x5"
5) "x6"
127.0.0.1:6379> 

# zrangebyscore key min max [withscores] [limit offset count] 根据分数大小,从小到大求取范围
127.0.0.1:6379> ZRANGEBYSCORE zset1 5 7 
1) "x5"
2) "x6"
3) "x7"

# zrangebyscore key min max [withscores] [limit offset count] 根据分数大小,从小到大求取范围
127.0.0.1:6379> 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 key start stop [withscores] 从大到小的按分数排序
127.0.0.1:6379> ZREVRANGE zset1 1 5 
1) "x8"
2) "x7"
3) "x6"
4) "x5"
5) "x4"

#  zrevrangebyscore key max min [withscores] 从大到小的按分数排序
127.0.0.1:6379> ZREVRANGEBYSCORE zset2 5 2 withscores
1) "y5"
2) "5"
3) "x4"
4) "4"
5) "y3"
6) "3"
7) "x2"
8) "2"

#  zrevrank key member 按从大到小的顺序,求元素的排行
127.0.0.1:6379> ZREVRANK zset1 x4
(integer) 5

# zscore key member 返回成员的分数值
127.0.0.1:6379> ZSCORE zset1 x5
"5"

# zunionstore desKey numKeys key1 [key2 key3 key4 …] 求多个有序集合的并集,其中 numKeys是有序,集合的个数
127.0.0.1:6379> ZUNIONSTORE des_key 2 zset1 zset2
(integer) 14

# zincrby key increment member 给有序集合成员值为 member 的分数增加 increment
127.0.0.1:6379> ZINCRBY zset1 5 x9
"14"

# zremrangebyscore key start stop 根据分数区间进行删除
127.0.0.1:6379> ZREMRANGEBYSCORE zset1 3 2
(integer) 0

# zremrangebyscore key start stop 根据分数区间进行删除
127.0.0.1:6379> ZREMRANGEBYSCORE zset1 3 5 
(integer) 3

# zremrangebyrank key start stop 按照分数排行从小到大的排序删除,从0开始计算
127.0.0.1:6379> ZREMRANGEBYRANK zset1 1 3 
(integer) 3

# zremrangebylex key min max  按照值的分布进行删除
127.0.0.1:6379> ZREMRANGEBYLEX zset2 [y1 [y5
(integer) 6
127.0.0.1:6379>
127.0.0.1:6379> ZCARD zset1
(integer) 3
127.0.0.1:6379> ZCARD zset2
(integer) 3
127.0.0.1:6379> 
127.0.0.1:6379> ZRANGE zset1 0 999
1) "x1"
2) "x8"
3) "x9"
127.0.0.1:6379> ZRANGE zset2  0 999
1) "y7"
2) "x8"
3) "y9"
127.0.0.1:6379> 



spring-data-redis 对有序集合的封装

在 Spring 中使用 Redis 的有序集合,需要注意的是 Spring 对 Redis 有序集合的元素的值和分数的范围( Range )和限制( Limit)进行了封装。

我们来看下Spring是如何封装的。 先介绍一个主要的接口一一TypedTuple,它不是一个普通的接口,而一个内部接口.

org.springframework . data. redis.core .ZSetOperations 接口的内部接口,它定义了两个方

在这里插入图片描述

  • getValue()是获取值, getScore()是获取分数,但是它只是一个接口,而不是一个实现类
  • spring-data-red is 提供了 一个默认的实现类一DefaultTypedTuple 在这里插入图片描述

在默认的情况下 Spring 就会把带有分数的有序集合的值和分数封装到这个类中 ,这样就可以通过这个类对象读取对应的值和分数了 .

Spring 不仅对有序集合元素封装,而且对范围也进行了封装,方便使用.它是使用接口 org.springframe.work.data.redis.connection.RedisZSetCommands 下的内部类 Range 进行封装的,它有一个静态的 range()方法,使用它就可以生成一个 Range 对象了,只是要清楚 Range对象的几个方法才行.
在这里插入图片描述

//  设置大于等于 min
public Range gte(Object min)
//  设置大于 min
public Range gt(Object min)
//  设置小于等于 max
public Range lte(Object max)
//  设置小于 max
public Range lt(Object max)

这 4 个方法就是最常用的范围方法.

下面看一下limit,它是接口 org.springframework.data.redis.connection.RedisZSetCommands 下的内部类,它是一个简单的 POJO,它存在两个属性
在这里插入图片描述

通过属性的名称很容易知道:offset 代表从第几个开始截取,而 count 代表限制返回的总数量。


使用 Spring 操作有序集合

刚才讨论了 spring-data-redis 项目对有序集合的封装,在此基础上这里的演示示例代码在测试代码前,要把 RedisTemplate 的 keySerializer 和 valueSerializer属性都修改为字符串序列化器 StringRedisSerializer

配置文件

<?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.zset;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.connection.RedisZSetCommands.Limit;
import org.springframework.data.redis.connection.RedisZSetCommands.Range;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;


public class SpringRedisZSetDemo {

	private static final String ZSET1 = "zset1";
	private static final String ZSET2 = "zset2";

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static void main(String[] args) {

		ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring/spring-redis-zset.xml");
		RedisTemplate redisTemplate = (RedisTemplate) ctx.getBean("redisTemplate");

		// 方便测试,清空数据
		redisTemplate.delete(ZSET1);
		redisTemplate.delete(ZSET2);

		// Spring 提供接口 TypedTuple 操作有序集合
		Set<TypedTuple> set1 = new HashSet<ZSetOperations.TypedTuple>();
		Set<TypedTuple> set2 = new HashSet<ZSetOperations.TypedTuple>();

		// 构造数据
		// 127.0.0.1:6379>
		// # zadd key score1 value1 [score2 value2 …] 向有序集合zset1 ,增加9个成员
		// >ZADD zset1 1 x1 2 x2 3 x3 4 x4 5 x5 6 x6 7 x7 8 x8 9 x9
		// (integer) 9

		// # zadd key score1 value1 [score2 value2 …] 向有序集合zset2 ,增加9个成员
		// > ZADD zset2 1 y1 2 x2 3 y3 4 x4 5 y5 6 x6 7 y7 8 x8 9 y9
		// (integer) 9
		int j = 9;
		
		String value1, value2 = null;
		double score1, score2 = 0.0;
		for (int i = 1; i <= 9; i++) {

			// 计算分数和值
			 score1 = Double.valueOf(i);
			 value1 = "x" + i;
			
			if (j > 0) {
				score2 = Double.valueOf(j);
				value2 = j % 2 == 1 ? "y" + j : "x" + j;
				j--;
			}
			// 使用 Spring提供的默认 TypedTuple-DefaultTypedTuple
			TypedTuple typedTuplel = new DefaultTypedTuple(value1,score1);
			set1.add(typedTuplel);
			TypedTuple typedTuple2 = new DefaultTypedTuple(value2,score2);
			set2.add(typedTuple2);

		}
		// 写入redis
		redisTemplate.opsForZSet().add(ZSET1, set1);
		redisTemplate.opsForZSet().add(ZSET2, set2);
		
		// 统计总数
		Long size = redisTemplate.opsForZSet().size(ZSET1);
		System.out.println(ZSET1 + "的size为" + size);

		// 计分数为 score ,那么下面的方法就是求 3<=score<=6 的元素
		Long count = redisTemplate.opsForZSet().count(ZSET1, 3, 6);
		System.out.println(ZSET1 + "中3<=score<=6 的count为" + count);

		// 从下标一开始截驭 5 个元素,但是不返回分数 , 每一个元素是 String
		Set set = redisTemplate.opsForZSet().range(ZSET1, 1, 5);
		printSet(set);

		// 截取集合所有元素,并且对集合按分数排序,并返回分数 , 每一个元素是 TypedTuple
		Set<TypedTuple> typedTuples = redisTemplate.opsForZSet().rangeWithScores(ZSET1, 0, -1);
		printTypedTuple(typedTuples);
		
		
		// 将 zsetl 和 zset2 两个集合的交集放入集合 inter_zset
		size = redisTemplate.opsForZSet().intersectAndStore(ZSET1, ZSET2, "inter_zset");
		System.out.println("inter_zset size:" + size);
		
		// 查看交集inter_zset中的数据
		set = redisTemplate.opsForZSet().range("inter_zset", 0, redisTemplate.opsForZSet().size("inter_zset"));
		printSet(set);

		// 区间
		Range range = Range.range();
		range.lt("x8");// 小于
		range.gt("x1");// 大于

		set = redisTemplate.opsForZSet().rangeByLex(ZSET1, range);
		printSet(set);

		range.lte("x8");// 小于等于
		range.gte("x1");// 大于等于

		set = redisTemplate.opsForZSet().rangeByLex(ZSET1, range);
		printSet(set);
		
		// 限制返回个数
		Limit limit = Limit.limit();
		// 限制返回个数
		limit.count(4);
		// 限制从第2个开始截取
		limit.offset(2);
		
		// 求区间内的元素,并限制返回 4 条
		set = redisTemplate.opsForZSet().rangeByLex(ZSET1, range, limit);
		printSet(set);

		// 求排行,排名第 1 返回 0 ,第 2 返回 1
		Long rank = redisTemplate.opsForZSet().rank(ZSET1, "x4");
		System.out.println("rank=" + rank);

		// 删除元素 , 返回删除个数
		size = redisTemplate.opsForZSet().remove(ZSET1, "x5", "x6");
		System.out.println("remove " + size + " 个元素");

		// 按照排行删除从 0 开始算起,这里将删除第排名第 2 和第 3 的元素
		size = redisTemplate.opsForZSet().removeRange(ZSET1, 1, 2);
		System.out.println("removeRange " + size + " 个元素");

		// 获取所有集合的元索和分数 , 以 -1 代表全部元素
		typedTuples = redisTemplate.opsForZSet().rangeWithScores(ZSET1, 0, -1);
		printTypedTuple(typedTuples);
		
		// 删除指定的元素
		size = redisTemplate.opsForZSet().remove(ZSET2, "y3", "y5");
		System.out.println("remove " + size + " 个元素");
		
		// 给集合中的一个元素的分数加上 11
		Double double1 = redisTemplate.opsForZSet().incrementScore(ZSET2, "y1", 11);
		printTypedTuple(redisTemplate.opsForZSet().rangeWithScores(ZSET2, 0, redisTemplate.opsForZSet().size(ZSET2)));
		
		// 从大到小排列
		typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(ZSET2, 0, 99);
		printTypedTuple(typedTuples);
	}
	
	@SuppressWarnings("rawtypes")
	public static void printTypedTuple(Set<TypedTuple> typedTuples) {
		if (typedTuples != null && typedTuples.isEmpty()) {
			return;
		}
		Iterator<TypedTuple> iterator = typedTuples.iterator();
		while (iterator.hasNext()) {
			TypedTuple typedTuple = iterator.next();
			System.out.println("{value =" + typedTuple.getValue() + ", score=" + typedTuple.getScore() + "}");
		}
		System.out.println("----------------------");
	}
	
	@SuppressWarnings("rawtypes")
	public static void printSet(Set set) {
		if (set != null && set.isEmpty()) {
			return;
		}
		Iterator iterator = set.iterator();
		while (iterator.hasNext()) {
			Object val = iterator.next();
			System.out.println(val + "\t");
		}
		System.out.println("----------------------");
	}
}

输出

INFO : org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@73a8dfcc: startup date [Wed Sep 26 23:26:54 CST 2018]; root of context hierarchy
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [spring/spring-redis-zset.xml]
zset1的size为9
zset1中3<=score<=6 的count为4
x2	
x3	
x4	
x5	
x6	
----------------------
{value =x1, score=1.0}
{value =x2, score=2.0}
{value =x3, score=3.0}
{value =x4, score=4.0}
{value =x5, score=5.0}
{value =x6, score=6.0}
{value =x7, score=7.0}
{value =x8, score=8.0}
{value =x9, score=9.0}
----------------------
inter_zset size:4
x2	
x4	
x6	
x8	
----------------------
x2	
x3	
x4	
x5	
x6	
x7	
----------------------
x1	
x2	
x3	
x4	
x5	
x6	
x7	
x8	
----------------------
x3	
x4	
x5	
x6	
----------------------
rank=3
remove 2 个元素
removeRange 2 个元素
{value =x1, score=1.0}
{value =x4, score=4.0}
{value =x7, score=7.0}
{value =x8, score=8.0}
{value =x9, score=9.0}
----------------------
remove 2 个元素
{value =x2, score=2.0}
{value =x4, score=4.0}
{value =x6, score=6.0}
{value =y7, score=7.0}
{value =x8, score=8.0}
{value =y9, score=9.0}
{value =y1, score=12.0}
----------------------
{value =y1, score=12.0}
{value =y9, score=9.0}
{value =x8, score=8.0}
{value =y7, score=7.0}
{value =x6, score=6.0}
{value =x4, score=4.0}
{value =x2, score=2.0}
----------------------



注意

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


代码

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值