Redis的数据结构及其应用场景

Redis的基本命令

127.0.0.1:6379> set key aaa     //设置键为key,值为aaa
OK
127.0.0.1:6379> get key       //获取key值为key的值
"aaa" 
127.0.0.1:6379> exists key    //判断key键是否存在
(integer) 1
127.0.0.1:6379> expire key 1000    设置key键的过期时间(单位/秒)
(integer) 1
127.0.0.1:6379> ttl key    //查看key的剩余过期时间(单位/秒)
(integer) 954
127.0.0.1:6379> type key   //获取key的类型
string
127.0.0.1:6379> expire key 1000   //重新设置key的过期时间
(integer) 1
127.0.0.1:6379> ttl key
(integer) 997
127.0.0.1:6379> persist key    //移除key的过期时间
(integer) 1
127.0.0.1:6379> ttl key    //-1表示永不过期
(integer) -1
127.0.0.1:6379> expire key 1000
(integer) 1
127.0.0.1:6379> ttl key
(integer) 996
127.0.0.1:6379> set key1 world
OK
127.0.0.1:6379> get key1
"world"
127.0.0.1:6379> randomkey    //随机返回一个key值
"key1"
127.0.0.1:6379> pttl key1   
(integer) -1
127.0.0.1:6379> pttl key   //返回key的过期时间(单位/毫秒)
(integer) 899033
127.0.0.1:6379> rename key key1   //将key更名为key1(会覆盖掉原先的key1)
OK
127.0.0.1:6379> get key1  
"aaa"
127.0.0.1:6379> get key
(nil)
127.0.0.1:6379> renamenx key1 key   //仅当key不存在时,才会将key1更名为key(防止覆盖掉原来的key),返回1表示成功,0表示失败
(integer) 1
127.0.0.1:6379> get key1
(nil)
127.0.0.1:6379> get key
"aaa"
127.0.0.1:6379> set key1 abc
OK
127.0.0.1:6379> get key1
"abc"
127.0.0.1:6379> renamenx key1 key
(integer) 0

1. 字符串string

1.1 命令行操作

//getrange 截取字符串
127.0.0.1:6379> set mykey "hello world"
OK
127.0.0.1:6379> get mykey
"hello world"
127.0.0.1:6379> getrange mykey 0 4
"hello"
127.0.0.1:6379> getrange mykey 0 -2 //从0截到倒数第二个字符
"hello worl"

//getset  将mykey设为新值,并返回旧值
127.0.0.1:6379> getset mykey "pangting"
"hello world"
127.0.0.1:6379> get mykey
"pangting"
127.0.0.1:6379>

//mget 获取key和mykey的值(获取多个key的值)
127.0.0.1:6379> mget key mykey
1) "xiaoming"
2) "pangting"

//setex  将key的值设为qinqiang 并设置100秒的过期时间
127.0.0.1:6379> setex key 100 qinqiang
OK
127.0.0.1:6379> get key
"qinqiang"
127.0.0.1:6379> pttl key    //返回key的过期时间(单位/毫秒)
(integer) 88048
127.0.0.1:6379>

//setnx 当key不存在时才会设置,存在时不做操作
127.0.0.1:6379> get key
"qq"
127.0.0.1:6379> setnx key aa
(integer) 0
127.0.0.1:6379> get key
"qq"
127.0.0.1:6379> strlen key  //strlen 返回key的值的字符串长度
(integer) 2

127.0.0.1:6379> mset key1 aa key2 bb key3 cc  //mset  同时设置多个键值对
OK
127.0.0.1:6379> mget key1 key2 key3 ad      //mget    获取多个key的值
1) "aa"
2) "bb"
3) "cc"
4) (nil)

127.0.0.1:6379> set key 1
OK
127.0.0.1:6379> get key
"1"
127.0.0.1:6379> incr key   //incr 自增1
(integer) 2
127.0.0.1:6379> incrby key 100  // incrby  将key值+100
(integer) 102
127.0.0.1:6379> decr key   //decr  自减1
(integer) 101
127.0.0.1:6379> decrby key 100  //decrby  key值减100
(integer) 1

//append  连接字符串,不存在时新增
127.0.0.1:6379> get key
(nil)
127.0.0.1:6379> append key aa
(integer) 2
127.0.0.1:6379> get key
"aa"
127.0.0.1:6379> append key bb
(integer) 4
127.0.0.1:6379> get key
"aabb"

1.2 java使用

package redis;

import org.junit.Test;
import redis.clients.jedis.*;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class RedisTest {
    private static Jedis jedis = new Jedis("localhost");
    //set方法nxxx参数,NX代表不存在时才设置,XX表示只有在key存在时才设置
    private final static String NOT_EXISTS = "NX";
    private final static String IS_EXISTS = "XX";

    //set方法expx参数,ex代表单位为秒,px代表单位为毫秒
    private final static String SECONDS = "EX";
    private final static String MILLI_SECONDS = "PX";

    @Test
    public void testKey() {
        //jedis.set("name1", "pangting");
        //jedis.set("age", "18", IS_EXISTS, SECONDS, 10);
        //当age不存在时,设置age的值并设置过期时间为10秒
        //System.out.println(jedis.set("age", "18", NOT_EXISTS, SECONDS, 10));
        System.out.println(jedis.type("name"));//获取key:name的类型
        //jedis.del("name");//删除key:name
        System.out.println(jedis.get("name"));//获取"name"对应的值
        System.out.println(jedis.randomKey());//返回一个随机的key
        Set<String> keys = jedis.keys("*");//返回所有的key
        System.out.println(keys.toString());

        System.out.println(jedis.ttl("name"));//返回key:name的过期时间(单位/秒);-1代表永不过期
        jedis.expireAt("name", 1588174437);//设置key:name在某个时间过期
        System.out.println(jedis.pttl("name"));//返回key:name的过期时间(单位/豪秒)
        //jedis.rename("name", "newName");//修改key:name为newName(会覆盖掉原来的newName)
        System.out.println(jedis.exists("name"));//判断key:name是否存在

        jedis.setex("name", 10, "pangting");//设置key的值和10秒的过期时间
        jedis.psetex("name", 1000L, "pangting");//设置key的值和1000毫秒的过期时间
        jedis.setnx("name", "pangting");//设置key的值,若key已存在,则设置失败并返回0,不存在则设置成功并返回1

    }

    @Test
    public void testString() {
        //jedis.append("name", "dashadiao");
        //批量设置key-value的值,当有一个key已存在时,设置失败并返回0
        //System.out.println(jedis.msetnx("name3","pangting","name4","dapangting"));
        System.out.println(jedis.incr("age"));//自增
        jedis.incrBy("age", 100);//增加100
        System.out.println(jedis.get("age"));
        jedis.decr("age");//自减
        jedis.decrBy("age", 50);//减50
        System.out.println(jedis.get("age"));
        //批量获取key的值
        List<String> mget = jedis.mget("name", "name3", "name4");
        System.out.println(mget.toString());
        System.out.println(jedis.strlen("name"));//返回value的长度

        System.out.println(jedis.getrange("name",0,-1));//根据索引截取value的值

    }

}

1.3 应用场景

1.3.1 实现分布式锁
//设置锁字符串键,若存在则设置失败
SETNX("couponcode","lock") == 1 //成功获取锁

SETNX(lockKey,'lock')==0//有人占用资源获取锁失败

业务处理完毕释放分布式锁
DEL(lockKey);

//设置锁字符串键的失效时间,防止宕机,系统运行意外,导致无法释放锁
PEXPIRE(lockKey, lockMilliSeconds)
1.3.2 实现库存扣减或者计数器
// 计数器
INCR key  //INCR readcount::{帖子ID}    每阅读一次
GET key     //GET readcount::{帖子ID}    获取阅读量

//库存操作
 incr key   //incr 自增1
 incrby key 100  // incrby  将key值+100
decr key   //decr  自减1
decrby key 100  //decrby  key值减100

2. 散列表Hash

2.1 命令行操作

127.0.0.1:6379> hmset user name1 xiaohong name2 xiaoqiang name3 xiaoting    //hmset  设置哈希表user的键值对
OK
127.0.0.1:6379> hmget user name1 name2 name3   //hmget  获取user中 key值为name1,name2,name3的值
1) "xiaohong"
2) "xiaoqiang"
3) "xiaoting"
127.0.0.1:6379> hsetnx user name1 xiaoxiao   //hsetnx 只有当user中的name1不存在时才设置值
(integer) 0
127.0.0.1:6379> hmget user name1 name2 name3
1) "xiaohong"
2) "xiaoqiang"
3) "xiaoting"
127.0.0.1:6379> hsetnx user name4 xiaoxiao
(integer) 1
127.0.0.1:6379> hmget user name1 name2 name3 name4
1) "xiaohong"
2) "xiaoqiang"
3) "xiaoting"
4) "xiaoxiao"
127.0.0.1:6379> hkeys user    //hkeys 返回user中的所有key
1) "name1"
2) "name2"
3) "name3"
4) "name4"
127.0.0.1:6379> hlen user   //返回key的数量
(integer) 4
127.0.0.1:6379> hvals user   //返回user中所有的值
1) "xiaohong"
2) "xiaoqiang"
3) "xiaoting"
4) "xiaoxiao"
127.0.0.1:6379> hset user age 20
(integer) 1
127.0.0.1:6379> hget user age
"20"
127.0.0.1:6379> hdel user name4
(integer) 1
127.0.0.1:6379> hexists user name4
(integer) 0
127.0.0.1:6379> hexists user name3
(integer) 1
127.0.0.1:6379> hgetall user   //hgetall  获取user中所有的键值对
1) "name1"
2) "xiaohong"
3) "name2"
4) "xiaoqiang"
5) "name3"
6) "xiaoting"
7) "age"
8) "20"
127.0.0.1:6379> hincrby user age 100   //hincrby 将user中key为age的值增加100
(integer) 120

2.2 java基本操作

    @Test
    public void testHash() {
        jedis.hset("myHash", "name", "xiaoming");
        jedis.hset("myHash", "age", "22");
        jedis.hset("myHash", "height", "178");
        System.out.println(jedis.hget("myHash", "name"));
        System.out.println(jedis.hget("myHash", "age"));
        System.out.println(jedis.hget("myHash", "height"));
        List<String> hmget = jedis.hmget("myHash", "name", "age", "height");//根据属性获取value
        System.out.println(hmget.toString());
        Map<String, String> map = jedis.hgetAll("myHash");//返回所有键值对
        System.out.println(map.toString());
        List<String> myHash = jedis.hvals("myHash");//返回所有value
        System.out.println(myHash.toString());
        System.out.println(jedis.hexists("myHash", "age"));

    }

3. 列表List

3.1 命令行操作

127.0.0.1:6379> lpush list eee  //在列表list首部添加元素eee
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "eee"
2) "aaa"
3) "bbb"
4) "ccc"
5) "ddd"
127.0.0.1:6379> rpush list aaa bbb   //rpush 向list列表尾部添加元素aaa和bbb
(integer) 2
127.0.0.1:6379> llen list    //获取列表长度
(integer) 2
127.0.0.1:6379> lrange list 0 -1    //根据索引范围获取列表元素
1) "aaa"
2) "bbb"
127.0.0.1:6379> rpush list ccc ddd eee
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "aaa"
2) "bbb"
3) "ccc"
4) "ddd"
5) "eee"
127.0.0.1:6379> rpushx list fff   // 只有列表list存在才会添加值fff
(integer) 6
127.0.0.1:6379> rpushx list1 fff    //列表list1不存在,添加失败
(integer) 0
127.0.0.1:6379> lrange list 0 -1
1) "aaa"
2) "bbb"
3) "ccc"
4) "ddd"
5) "eee"
6) "fff"
127.0.0.1:6379> rpop list  //rpop 移除并返回列表的最后一个元素
"fff"
127.0.0.1:6379> lrange list 0 -1
1) "aaa"
2) "bbb"
3) "ccc"
4) "ddd"
5) "eee"
127.0.0.1:6379> lpop list   //lpop 移除并返回列表的第一个元素
"aaa"
127.0.0.1:6379> lrange list 0 -1
1) "bbb"
2) "ccc"
3) "ddd"
4) "eee"
127.0.0.1:6379> lindex list 1   // 根据索引获取列表的值
"ccc"
127.0.0.1:6379> blpop list 10   // 阻塞方式移除并获取列表的第一个元素,10秒内获取失败(即列表为空)则返回nil和时间
1) "list"
2) "bbb"
127.0.0.1:6379> blpop list1 10
(nil)
(10.03s)
127.0.0.1:6379> brpop list 10// 阻塞方式移除并获取列表的最后一个元素,10秒内获取失败(即列表为空)则返回nil和时间
1) "list"
2) "eee"
127.0.0.1:6379> brpop list1 10
(nil)
(10.04s)
127.0.0.1:6379> lrange list 0 -1
1) "ccc"
2) "ddd"
127.0.0.1:6379> linsert list before ccc aaa  // 在ccc元素之前插入aaa
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "aaa"
2) "ccc"
3) "ddd"
127.0.0.1:6379> linsert list after aaa bbb  //在aaa元素之后插入元素bbb
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "aaa"
2) "bbb"
3) "ccc"
4) "ddd"

3.2 java基本操作

    @Test
    public void testList() {
        jedis.lpush("myList", "aa", "bb", "cc");//left-push  : [cc, bb, aa]
        System.out.println(jedis.lrange("myList",0,-1).toString());//根据索引范围获取List的全部元素
        jedis.rpush("myList", "dd", "ee");//right-push   [cc, bb, aa, dd, ee]
        jedis.lpop("myList");//向左移出队列一个元素  [bb, aa, dd, ee]
        jedis.rpop("myList");//向右移出队列一个元素  [bb, aa, dd]

        jedis.blpop(10, "myList");//block-left-pop  阻塞方式向左出队,10秒后超时   [aa, dd]
        jedis.brpop(10, "myList");//block-right-pop  阻塞方式向右出队,10秒后超时  [aa]

        jedis.linsert("myList", Client.LIST_POSITION.BEFORE, "aa", "abc");//在aa元素之前插入abc
        System.out.println(jedis.lrange("myList",0,-1).toString());  //[abc, aa]
        Long length = jedis.llen("myList");//获取集合长度
        String value = jedis.lindex("myList", 1);//根据索引获取元素


        jedis.flushAll();
    }

3.3 应用场景

3.3.1 实现消息队列
List列表,先进先出,从队首取值,从队尾存值;反过来也可以
3.3.2 实现栈的数据结构
栈(lpush-->lpop或者rpush-->rpop)的数据结构

4. 无序集合Set:

4.1 命令行操作

127.0.0.1:6379> sadd myset aa bb cc aa dd bb   //sadd  添加set元素(元素不能重复) 
(integer) 4
127.0.0.1:6379> scard myset   // 返回set元素的数量
(integer) 4
127.0.0.1:6379> smembers myset   // 返回set中的元素
1) "dd"
2) "cc"
3) "bb"
4) "aa"
127.0.0.1:6379> sismember myset aa   // 判断元素aa是否在set中
(integer) 1
127.0.0.1:6379> sismember myset ab
(integer) 0
127.0.0.1:6379> spop myset   // 随机删除一个元素并返回
"dd"
127.0.0.1:6379> srem myset aa bb dd   //删除元素aa bb dd(已被删除,所以删除成功的数量是2) 
(integer) 2
127.0.0.1:6379>


4.2 java基本操作

    /**
     * 无序集合
     */
    @Test
    public void testSet() {
        jedis.sadd("mySet", "aa", "bb", "cc", "aa", "dd");//[dd, bb, cc, aa]
        Long setLong = jedis.scard("mySet");//获取set的长度
        Set<String> mySet1 = jedis.smembers("mySet");//获取所有set的元素
        System.out.println(mySet1.toString());

        jedis.sadd("mySet1", "aa");
        jedis.sadd("mySet2", "dd");

        Set<String> sdiff = jedis.sdiff("mySet", "mySet1", "mySet2");//取差集 mySet - mySet1 - mySet2
        System.out.println(sdiff.toString());//[bb, cc]

        Set<String> sinter = jedis.sinter("mySet", "mySet1");//取交集
        System.out.println(sinter.toString());//[aa]

        Set<String> sunion = jedis.sunion("mySet", "mySet1");//取并集
        System.out.println(sunion.toString());

        jedis.sunionstore("newSet", "mySet", "mySet1");//将mySet和mySet1的并集保存入新的newSet中

        String mySet = jedis.spop("mySet");//随机移除一个元素并返回
        jedis.srem("mySet", "aa", "bb"); //删除mySet中的aa和bb元素


        jedis.flushAll();
    }

4.3 应用场景

4.3.1 实现抽奖
  1. 像转发微博,直播刷礼物时都会有一个抽奖的操作, 将这些满足条件的用户添加入set中
SADD key user1 user2 user3 ...    //将用户ID添加入set中
  1. 随机弹出count名用户并将其从set中删除, 实现了抽奖的操作
SPOP key [count]  // 抽取count名用户
4.3.2 获取共同好友

获取共同好友就是取交集的操作了

 jedis.sinter("mySet", "mySet1");//取交集

像其他的取并集取差集等的,也可以通过set来实现

5. 有序集合SortSet

5.1 java基本操作


    /**
     * 有序集合
     */
    @Test
    public void testSortSet() {
        Map<String, Double> map = new HashMap<>();
        map.put("aaa", 1.0);
        map.put("bbb", 1.2);
        map.put("ccc", -1.2);
        map.put("ddd", 0.0);
        jedis.zadd("mySortSet", map);

        Set<String> mySortSet = jedis.zrange("mySortSet", 0, -1);
        System.out.println(mySortSet);//[ccc, ddd, aaa, bbb]  从小到大排序

        jedis.flushAll();

    }
    

5.2 应用场景

5.2.1 实现延时队列

延时队列:使用有序集合,score参数保存时间戳,会按照时间戳排序,
开一个线程不断地取第一个数据,当前时间大于时间戳时则拿出第一个key来运行

5.2.2 带有权重的场景:排行榜等

6. HyperLogLog

6.1 介绍

HyperLogLog 可以接受多个元素作为输入,并给出输入元素的基数估算值:
• 基数:集合中不同元素的数量。比如 {‘apple’, ‘banana’, ‘cherry’, ‘banana’, ‘apple’} 的基数就是 3 。
• 估算值:算法给出的基数并不是精确的,可能会比实际稍微多一些或者稍微少一些,但会控制在合
理的范围之内。
HyperLogLog 的优点是,即使输入元素的数量或者体积非常非常大,计算基数所需的空间总是固定
的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基
数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以
HyperLogLog 不能像集合那样,返回输入的各个元素。

6.2 命令行使用

127.0.0.1:6379> pfadd myHyperLogLog a a a b b b c c c d e  // 添加元素
(integer) 1
127.0.0.1:6379> PFCOUNT myHyperLogLog   // 获取基数
(integer) 5
127.0.0.1:6379> pfadd myHyperLogLog2 a b e f g
(integer) 1
127.0.0.1:6379> PFCOUNT myHyperLogLog2
(integer) 5
//将myHyperLogLog 和myHyperLogLog2合并成为一个新的myHyperLogLog3
127.0.0.1:6379> PFMERGE myHyperLogLog3 myHyperLogLog myHyperLogLog2 
OK
127.0.0.1:6379> PFCOUNT myHyperLogLog3
(integer) 7

6.3 java使用

    @Test
    public void testHyperLogLog() {
        jedis.flushAll();
        jedis.pfadd("myHyperLogLog", "a", "b", "c");//添加元素
        jedis.pfadd("myHyperLogLog2", "c", "d", "e");
        System.out.println(jedis.pfcount("myHyperLogLog"));//获取基数   3
        System.out.println(jedis.pfcount("myHyperLogLog2")); //3
        System.out.println(jedis.pfcount("myHyperLogLog2","myHyperLogLog"));//获取两个集合合并之后的基数    5
        //将myHyperLogLog 和myHyperLogLog2合并成为一个新的myHyperLogLog3
        jedis.pfmerge("myHyperLogLog3","myHyperLogLog2", "myHyperLogLog");//5
        System.out.println(jedis.pfcount("myHyperLogLog3"));
    }

6.4 应用场景

适用的场景都是一个大集合中,找出不重复的基数数量, 而且对结果的准确度没有很大要求的情况

6.4.1 记录网站每天访问的独立IP数量
pfadd ips ip1 ip2 ip3 ... // 添加ip
pfcount ips   // 获取ip数量
6.4.2 记录网站每天访问的会员数量(同理)

6.5 HyperLogLog原理?

待补充

7. bitmap

Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset)
在这里插入图片描述

7.1 命令行基本操作

127.0.0.1:6379> SETBIT myBitMap 100 1  //将offset=100设为1
(integer) 0
127.0.0.1:6379> BITCOUNT myBitMap   //获取位数为1的总数
(integer) 1
127.0.0.1:6379> GETBIT myBitMap 100   // 获取offset为100的值
(integer) 1

7.2 应用场景
7.2.1 用户签到
    @Test
    public void testBitMap() {
        jedis.flushAll();
        for (int userId = 0; userId < 10000; userId++) {
            if (userId % 2 == 0) {
                //模拟签到,将用户ID设为偏移位offset
                jedis.setbit("sign_user_2020_05_01", userId, true);
            }
        }
        Long sign_user_2020_05_01 = jedis.bitcount("sign_user_2020_05_01");//获取1的数量,即签到用户的数量
        System.out.println(sign_user_2020_05_01);
        System.out.println(jedis.getbit("sign_user_2020_05_01",112));//查看112用户是否签到
        for (int userId = 0; userId < 10000; userId++) {
            if (userId % 2 != 0) {
                //模拟签到,将用户ID设为偏移位offset
                jedis.setbit("sign_user_2020_05_02", userId, true);
            }
        }

         //逻辑与运算:获取5.1和5.2都签到的用户
        jedis.bitop(BitOP.AND, "sign_user_2020_05_02_and_2020_05_01", "sign_user_2020_05_02", "sign_user_2020_05_01");
        //逻辑或:获取5.1和5.2签到的用户总数
        jedis.bitop(BitOP.OR, "sign_user_2020_05_02_or_2020_05_01", "sign_user_2020_05_02", "sign_user_2020_05_01");
        //逻辑非:获取5.1签到但是5.2不登录的用户数
        jedis.bitop(BitOP.NOT, "sign_user_2020_05_01_not_2020_05_02", "sign_user_2020_05_01", "sign_user_2020_05_02");
        
        System.out.println(jedis.bitcount("sign_user_2020_05_02_and_2020_05_01"));
    }
bitmap优点
  1. 节约空间,统计一亿人每天的登录情况,用一亿bit,约1200WByte,约10M的字符就能表示(因为bitop命令的返回值是保存到time中的字符串的长度(以字节byte为单位),和输入 key 中最长的字符串长度相等。即1亿除以8bit=1250万Byte);
  2. 计算方便。

性能:
  如果你的 bitmap 数据非常大,那么可以考虑使用以下两种方法:

● 将一个大的 bitmap 分散到不同的 key 中,作为小的 bitmap 来处理。使用 Lua 脚本可以很方便地完成这一工作。
● 使用 BITCOUNT 的 start 和 end 参数,每次只对所需的部分位进行计算,将位的累积工作(accumulating)放到客户端进行,并且对结果进行缓存 (caching)。

何时使用:

如果活跃用户在百万级别,使用Redis BitMap很划算。
  如果活跃用户很少,而用户id都是10位以上的int(即offset最大值为100亿以上,大约就要消耗1G的内存)。那就很浪费内存了,还不如使用set集合,然后求交集就可以了。

8. Geo(地理空间)

待补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值