Redis链表的使用

表(List)

头元素和尾元素

头元素指的是列表左端/前端第一个元素,尾元素指的是列表右端/后端第一个元素。

举个例子,列表list包含三个元素:x, y, z,其中x是头元素,而z则是尾元素。

空列表

指不包含任何元素的列表,Redis将不存在的key也视为空列表。

LPUSH

LPUSH key value [value ...]

将一个或多个值value插入到列表key表头

如果有多个value值,那么各个value值按从左到右的顺序依次插入到表头:比如对一个空列表(mylist)执行LPUSH mylist a b c,则结果列表为c b a,等同于执行执行命令LPUSH mylist aLPUSH mylist bLPUSH mylist c

如果key不存在,一个空列表会被创建并执行LPUSH操作。

key存在但不是列表类型时,返回一个错误。

时间复杂度:
O(1)
返回值:
执行 LPUSH命令后,列表的长度。

注解

在Redis 2.4版本以前的LPUSH命令,都只接受单个value值。

# 加入单个元素

redis> LPUSH languages python
(integer) 1

# 加入重复元素

redis> LPUSH languages python
(integer) 2

redis> LRANGE languages 0 -1 # 列表允许重复元素
1) "python"
2) "python"

# 加入多个元素

redis> LPUSH mylist a b c
(integer) 3

redis> LRANGE mylist 0 -1
1) "c"
2) "b"
3) "a"

LPUSHX

LPUSHX key value

将值value插入到列表key的表头,当且仅当key存在并且是一个列表。

LPUSH命令相反,当key不存在时,LPUSHX命令什么也不做。

时间复杂度:
O(1)
返回值:
LPUSHX命令执行之后,表的长度。
# 情况1:对空列表执行LPUSHX

redis> LLEN greet    # greet是一个空列表
(integer) 0

redis> LPUSHX greet "hello"  # 尝试LPUSHX,失败,因为列表为空
(integer) 0


# 情况2:对非空列表执行LPUSHX

redis> LPUSH greet "hello"   # 先用LPUSH创建一个有一个元素的列表
(integer) 1

redis> LPUSHX greet "good morning"   # 这次LPUSHX执行成功
(integer) 2

redis> LRANGE greet 0 -1
1) "good morning"
2) "hello"

RPUSH

RPUSH key value [value ...]

将一个或多个值value插入到列表key表尾

如果有多个value值,那么各个value值按从左到右的顺序依次插入到表尾:比如对一个空列表(mylist)执行RPUSH mylist a b c,则结果列表为a b c,等同于执行命令RPUSH mylist aRPUSH mylist bRPUSH mylist c

如果key不存在,一个空列表会被创建并执行RPUSH操作。

key存在但不是列表类型时,返回一个错误。

时间复杂度:
O(1)
返回值:
执行 RPUSH操作后,表的长度。

注解

在Redis 2.4版本以前的RPUSH命令,都只接受单个value值。

# 添加单个元素

redis> RPUSH languages c
(integer) 1

# 添加重复元素

redis> RPUSH languages c
(integer) 2

redis> LRANGE languages 0 -1 # 列表允许重复元素
1) "c"
2) "c"

# 添加多个元素

redis> RPUSH mylist a b c
(integer) 3

redis> LRANGE mylist 0 -1
1) "a"
2) "b"
3) "c"

RPUSHX

RPUSHX key value

将值value插入到列表key的表尾,当且仅当key存在并且是一个列表。

RPUSH命令相反,当key不存在时,RPUSHX命令什么也不做。

时间复杂度:
O(1)
返回值:
RPUSHX命令执行之后,表的长度。
# 情况1:key不存在

redis> LLEN greet
(integer) 0

redis> RPUSHX greet "hello"  # 对不存在的key进行RPUSHX,PUSH失败。
(integer) 0


# 情况2:key存在且是一个非空列表

redis> RPUSH greet "hi"  # 先用RPUSH插入一个元素
(integer) 1

redis> RPUSHX greet "hello"  # greet现在是一个列表类型,RPUSHX操作成功。
(integer) 2

redis> LRANGE greet 0 -1
1) "hi"
2) "hello"

LPOP

LPOP key

移除并返回列表key的头元素。

时间复杂度:
O(1)
返回值:
列表的头元素。
key不存在时,返回 nil
redis> LLEN course
(integer) 0

redis> RPUSH course algorithm001
(integer) 1
redis> RPUSH course c++101
(integer) 2

redis> LPOP course  # 移除头元素
"algorithm001"

RPOP

RPOP key

移除并返回列表key的尾元素。

时间复杂度:
O(1)
返回值:
列表的尾元素。
key不存在时,返回 nil
redis> RPUSH mylist "one"
(integer) 1
redis> RPUSH mylist "two"
(integer) 2
redis> RPUSH mylist "three"
(integer) 3

redis> RPOP mylist  # 返回被弹出的元素
"three"

redis> LRANGE mylist 0 -1   # 列表剩下的元素
1) "one"
2) "two"

BLPOP

BLPOP key [key ...] timeout

BLPOP是列表的阻塞式(blocking)弹出原语。

它是LPOP命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被BLPOP命令阻塞,直到等待超时或发现可弹出元素为止。

当给定多个key参数时,按参数key的先后顺序依次检查各个列表,弹出第一个非空列表的头元素。

非阻塞行为

BLPOP被调用时,如果给定key内至少有一个非空列表,那么弹出遇到的第一个非空列表的头元素,并和被弹出元素所属的列表的名字一起,组成结果返回给调用者。

当存在多个给定key时,BLPOP按给定key参数排列的先后顺序,依次检查各个列表。

假设现在有job commandrequest三个列表,其中job不存在,commandrequest都持有非空列表。考虑以下命令:

BLPOP job command request 0

BLPOP保证返回的元素来自command,因为它是按”查找job -> 查找command -> 查找request“这样的顺序,第一个找到的非空列表。

redis> DEL job command request  # 确保key都被删除
(integer) 0
redis> LPUSH command "update system..."  # 为command列表增加一个值
(integer) 1
redis> LPUSH request "visit page"  # 为request列表增加一个值
(integer) 1

redis> BLPOP job command request 0  # job列表为空,被跳过,紧接着command列表的第一个元素被弹出。
1) "command"    # 弹出元素所属的列表
2) "update system..."   # 弹出元素所属的值

阻塞行为

如果所有给定key都不存在或包含空列表,那么BLPOP命令将阻塞连接,直到等待超时,或有另一个客户端对给定key的任意一个执行LPUSHRPUSH命令为止。

超时参数timeout接受一个以秒为单位的数字作为值。超时参数设为0表示阻塞时间可以无限期延长(block indefinitely) 。

redis> EXISTS job  # 确保两个key都不存在
(integer) 0
redis> EXISTS command
(integer) 0

redis> BLPOP job command 300  #因为key一开始不存在,所以操作会被阻塞,直到另一客户端对job或者command列表进行PUSH操作。
1) "job"  # 这里被push的是job
2) "do my home work"  # 被弹出的值
(26.26s)  # 等待的秒数

redis> BLPOP job command 5  # 等待超时的情况
(nil)
(5.66s) # 等待的秒数

相同的key被多个客户端同时阻塞

相同的 key可以被多个客户端同时阻塞。
不同的客户端被放进一个队列中,按”先阻塞先服务”(first-BLPOP,first-served)的顺序为 key执行 BLPOP命令。

在MULTI/EXEC事务中的BLPOP

BLPOP可以用于流水线(pipline,批量地发送多个命令并读入多个回复),但把它用在MULTI/EXEC块当中没有意义。因为这要求整个服务器被阻塞以保证块执行时的原子性,该行为阻止了其他客户端执行LPUSHRPUSH命令。

因此,一个被包裹在MULTI/EXEC块内的BLPOP命令,行为表现得就像LPOP一样,对空列表返回nil,对非空列表弹出列表元素,不进行任何阻塞操作。

# 情况1:对非空列表进行操作

redis> RPUSH job programming
(integer) 1

redis> MULTI
OK

redis> BLPOP job 30
QUEUED

redis> EXEC  # 不阻塞,立即返回
1) 1) "job"
   2) "programming"


# 情况2:对空列表进行操作

redis> LLEN job  # 空列表
(integer) 0

redis> MULTI
OK

redis> BLPOP job 30
QUEUED

redis> EXEC  # 不阻塞,立即返回
1) (nil)
时间复杂度:
O(1)
返回值:
如果列表为空,返回一个 nil
反之,返回一个含有两个元素的列表,第一个元素是被弹出元素所属的 key,第二个元素是被弹出元素的值。

BRPOP

BRPOP key [key ...] timeout

BRPOP是列表的阻塞式(blocking)弹出原语。

它是RPOP命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被BRPOP命令阻塞,直到等待超时或发现可弹出元素为止。

当给定多个key参数时,按参数key的先后顺序依次检查各个列表,弹出第一个非空列表的尾部元素。

关于阻塞操作的更多信息,请查看BLPOP命令,BRPOP除了弹出元素的位置和BLPOP不同之外,其他表现一致。

时间复杂度:
O(1)
返回值:
假如在指定时间内没有任何元素被弹出,则返回一个 nil和等待时长。
反之,返回一个含有两个元素的列表,第一个元素是被弹出元素所属的 key,第二个元素是被弹出元素的值。
redis> LLEN course
(integer) 0

redis> RPUSH course algorithm001
(integer) 1
redis> RPUSH course c++101  # 尾部元素
(integer) 2

redis> BRPOP course 30
1) "course" # 弹出元素的key
2) "c++101" # 弹出元素的值

LLEN

LLEN key

返回列表key的长度。

如果key不存在,则key被解释为一个空列表,返回0.

如果key不是列表类型,返回一个错误。

时间复杂度:
O(1)
返回值:
列表 key的长度。
# 情况1:空列表

redis> LLEN job
(integer) 0


# 情况2:非空列表

redis> LPUSH job "cook food"
(integer) 1
redis> LPUSH job "have lunch"
(integer) 2

redis> LLEN job
(integer) 2

LRANGE

LRANGE key start stop

返回列表key中指定区间内的元素,区间以偏移量startstop指定。

下标(index)参数startstop都以0为底,也就是说,以0表示列表的第一个元素,以1表示列表的第二个元素,以此类推。

你也可以使用负数下标,以-1表示列表的最后一个元素,-2表示列表的倒数第二个元素,以此类推。

注意LRANGE命令和编程语言区间函数的区别

假如你有一个包含一百个元素的列表,对该列表执行LRANGE list 0 10,结果是一个包含11个元素的列表,这表明stop下标也在LRANGE命令的取值范围之内(闭区间),这和某些语言的区间函数可能不一致,比如Ruby的Range.newArray#slice和Python的range()函数。

超出范围的下标

超出范围的下标值不会引起错误。

如果start下标比列表的最大下标end(LLEN list减去1)还要大,或者start > stopLRANGE返回一个空列表。

如果stop下标比end下标还要大,Redis将stop的值设置为end

时间复杂度:
O(S+N), S为偏移量 startN为指定区间内元素的数量。
返回值:
一个列表,包含指定区间内的元素。
redis> RPUSH fp-language lisp   # 插入一个值到列表fp-language
(integer) 1
redis> LRANGE fp-language 0 0
1) "lisp"

redis> RPUSH fp-language scheme
(integer) 2
redis> LRANGE fp-language 0 1
1) "lisp"
2) "scheme"

LREM

LREM key count value

根据参数count的值,移除列表中与参数value相等的元素。

count的值可以是以下几种:
  • count > 0: 从表头开始向表尾搜索,移除与value相等的元素,数量为count
  • count < 0: 从表尾开始向表头搜索,移除与value相等的元素,数量为count的绝对值。
  • count = 0: 移除表中所有与value相等的值。
时间复杂度:
O(N), N为列表的长度。
返回值:
被移除元素的数量。
因为不存在的 key被视作空表(empty list),所以当 key不存在时, LREM命令总是返回 0
# 先创建一个表,内容排列是
# morning hello morning helllo morning

redis> LPUSH greet "morning"
(integer) 1
redis> LPUSH greet "hello"
(integer) 2
redis> LPUSH greet "morning"
(integer) 3
redis> LPUSH greet "hello"
(integer) 4
redis> LPUSH greet "morning"
(integer) 5

redis> LRANGE greet 0 4 # 查看所有元素
1) "morning"
2) "hello"
3) "morning"
4) "hello"
5) "morning"

redis> LREM greet 2 morning  # 移除从表头到表尾,最先发现的两个morning
(integer) 2  # 两个元素被移除

redis> LLEN greet   # 还剩3个元素
(integer) 3

redis> LRANGE greet 0 2
1) "hello"
2) "hello"
3) "morning"

redis> LREM greet -1 morning  # 移除从表尾到表头,第一个morning
(integer) 1

redis> LLEN greet
(integer) 2

redis> LRANGE greet 0 1
1) "hello"
2) "hello"

redis> LREM greet 0 hello  # 移除表中所有hello
(integer) 2  # 两个hello被移除

redis> LLEN greet
(integer) 0

LSET

LSET key index value

将列表key下标为index的元素的值甚至为value

更多信息请参考LINDEX操作。

index参数超出范围,或对一个空列表(key不存在)进行LSET时,返回一个错误。

时间复杂度:
对头元素或尾元素进行 LSET操作,复杂度为O(1)。
其他情况下,为O(N), N为列表的长度。
返回值:
操作成功返回 ok,否则返回错误信息。
# 情况1:对空列表(key不存在)进行LSET

redis> EXISTS list
(integer) 0

redis> LSET list 0 item
(error) ERR no such key


# 情况2:对非空列表进行LSET

redis> LPUSH job "cook food"
(integer) 1

redis> LRANGE job 0 0
1) "cook food"

redis> LSET job 0 "play game"
OK

redis> LRANGE job  0 0
1) "play game"


# 情况3:index超出范围

redis> LLEN list # 列表长度为1
(integer) 1

redis> LSET list 3 'out of range'
(error) ERR index out of range

LTRIM

LTRIM key start stop

对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。

举个例子,执行命令LTRIM list 0 2,表示只保留列表list的前三个元素,其余元素全部删除。

下标(index)参数startstop都以0为底,也就是说,以0表示列表的第一个元素,以1表示列表的第二个元素,以此类推。

你也可以使用负数下标,以-1表示列表的最后一个元素,-2表示列表的倒数第二个元素,以此类推。

key不是列表类型时,返回一个错误。

LTRIM命令通常和LPUSH命令或RPUSH命令配合使用,举个例子:

LPUSH log newest_log
LTRIM log 0 99

这个例子模拟了一个日志程序,每次将最新日志newest_log放到log列表中,并且只保留最新的100项。注意当这样使用LTRIM命令时,时间复杂度是O(1),因为平均情况下,每次只有一个元素被移除。

注意LTRIM命令和编程语言区间函数的区别

假如你有一个包含一百个元素的列表list,对该列表执行LTRIM list 0 10,结果是一个包含11个元素的列表,这表明stop下标也在LTRIM命令的取值范围之内(闭区间),这和某些语言的区间函数可能不一致,比如Ruby的Range.newArray#slice和Python的range()函数。

超出范围的下标

超出范围的下标值不会引起错误。

如果start下标比列表的最大下标end(LLEN list减去1)还要大,或者start > stopLTRIM返回一个空列表(因为LTRIM已经将整个列表清空)。

如果stop下标比end下标还要大,Redis将stop的值设置为end

时间复杂度:
O(N), N为被移除的元素的数量。
返回值:
命令执行成功时,返回 ok
# 情况1:一般情况下标

redis> LRANGE alpha 0 -1 # 建立一个5元素的列表
1) "h"
2) "e"
3) "l"
4) "l"
5) "o"

redis> LTRIM alpha 1 -1  # 删除索引为0的元素
OK

redis> LRANGE alpha 0 -1 # "h"被删除
1) "e"
2) "l"
3) "l"
4) "o"


# 情况2:stop下标比元素的最大下标要大

redis> LTRIM alpha 1 10086
OK
redis> LRANGE alpha 0 -1
1) "l"
2) "l"
3) "o"


# 情况3:start和stop下标都比最大下标要大,且start < sotp

redis> LTRIM alpha 10086 200000
OK
redis> LRANGE alpha 0 -1 # 整个列表被清空,等同于DEL alpha
(empty list or set)


# 情况4:start > stop

redis> LRANGE alpha 0 -1 # 在新建一个列表
1) "h"
2) "u"
3) "a"
4) "n"
5) "g"
6) "z"

redis> LTRIM alpha 10086 4
OK

redis> LRANGE alpha 0 -1 # 列表同样被清空
(empty list or set)

LINDEX

LINDEX key index

返回列表key中,下标为index的元素。

下标(index)参数startstop都以0为底,也就是说,以0表示列表的第一个元素,以1表示列表的第二个元素,以此类推。

你也可以使用负数下标,以-1表示列表的最后一个元素,-2表示列表的倒数第二个元素,以此类推。

如果key不是列表类型,返回一个错误。

时间复杂度:
O(N), N为到达下标 index过程中经过的元素数量。
因此,对列表的头元素和尾元素执行 LINDEX命令,复杂度为O(1)。
返回值:
列表中下标为 index的元素。
如果 index参数的值不在列表的区间范围内(out of range),返回 nil
redis> LPUSH mylist "World"
(integer) 1

redis> LPUSH mylist "Hello"
(integer) 2

redis> LINDEX mylist 0
"Hello"

redis> LINDEX mylist -1
"World"

redis> LINDEX mylist 3  # index不在mylist的区间范围内
(nil)

LINSERT

LINSERT key BEFORE|AFTER pivot value

将值value插入到列表key当中,位于值pivot之前或之后。

pivot不存在于列表key时,不执行任何操作。

key不存在时,key被视为空列表,不执行任何操作。

如果key不是列表类型,返回一个错误。

时间复杂度:
O(N), N为寻找 pivot过程中经过的元素数量。
返回值:
如果命令执行成功,返回插入操作完成之后,列表的长度。
如果没有找到 pivot,返回 -1
如果 key不存在或为空列表,返回 0
redis> RPUSH mylist "Hello"
(integer) 1
redis> RPUSH mylist "World"
(integer) 2

redis> LINSERT mylist BEFORE "World" "There"
(integer) 3

redis> LRANGE mylist 0 -1
1) "Hello"
2) "There"
3) "World"

redis> LINSERT mylist BEFORE "go" "let's"    # 对一个非空列表插入,查找一个不存在的pivot
(integer) -1    # 失败

redis> EXISTS fake_list  # 对一个空列表执行LINSERT命令
(integer) 0

redis> LINSERT fake_list BEFORE "nono" "gogogog"
(integer) 0 # 失败

RPOPLPUSH

RPOPLPUSH source destination

命令RPOPLPUSH在一个原子时间内,执行以下两个动作:

  • 将列表source中的最后一个元素(尾元素)弹出,并返回给客户端。
  • source弹出的元素插入到列表destination,作为destination列表的的头元素。

举个例子,你有两个列表sourcedestinationsource列表有元素a, b, cdestination列表有元素x, y, z,执行RPOPLPUSH source destination之后,source列表包含元素a, bdestination列表包含元素c, x, y, z ,并且元素c被返回。

如果source不存在,值nil被返回,并且不执行其他动作。

如果sourcedestination相同,则列表中的表尾元素被移动到表头,并返回该元素,可以把这种特殊情况视作列表的旋转(rotation)操作。

时间复杂度:
O(1)
返回值:
被弹出的元素。
# 相关数据

redis> RPUSH alpha a
(integer) 1
redis> RPUSH alpha b
(integer) 2
redis> RPUSH alpha c
(integer) 3
redis> RPUSH alpha d
(integer) 4

# 情况1:source和destination不同

redis> LRANGE alpha 0 -1 # 查看所有元素
1) "a"
2) "b"
3) "c"
4) "d"

redis> RPOPLPUSH alpha reciver   # 执行一次RPOPLPUSH看看
"d"

redis> LRANGE alpha 0 -1
1) "a"
2) "b"
3) "c"

redis> LRANGE reciver 0 -1
1) "d"

redis> RPOPLPUSH alpha reciver   # 再执行一次,确保rpop和lpush的位置正确
"c"

redis> LRANGE alpha 0 -1
1) "a"
2) "b"

redis> LRANGE reciver 0 -1
1) "c"
2) "d"


# 情况2:source和destination相同

redis> RPOPLPUSH alpha alpha
"c"

redis> LRANGE alpha 0 -1 # 原来的尾元素"c"被放到了头部
1) "c"
2) "a"
3) "b"

设计模式: 一个安全的队列

Redis的列表经常被用作队列(queue),用于在不同程序之间有序地交换消息(message)。一个程序(称之为生产者,producer)通过LPUSH命令将消息放入队列中,而另一个程序(称之为消费者,consumer)通过RPOP命令取出队列中等待时间最长的消息。

不幸的是,在这个过程中,一个消费者可能在获得一个消息之后崩溃,而未执行完成的消息也因此丢失。

使用RPOPLPUSH命令可以解决这个问题,因为它在返回一个消息之余,还将该消息添加到另一个列表当中,另外的这个列表可以用作消息的备份表:假如一切正常,当消费者完成该消息的处理之后,可以用LREM命令将该消息从备份表删除。

另一方面,助手(helper)程序可以通过监视备份表,将超过一定处理时限的消息重新放入队列中去(负责处理该消息的消费者可能已经崩溃),这样就不会丢失任何消息了。

BRPOPLPUSH

BRPOPLPUSH source destination timeout

BRPOPLPUSHRPOPLPUSH的阻塞版本,当给定列表source不为空时,BRPOPLPUSH的表现和RPOPLPUSH一样。

当列表source为空时,BRPOPLPUSH命令将阻塞连接,直到等待超时,或有另一个客户端对source执行LPUSHRPUSH命令为止。

超时参数timeout接受一个以秒为单位的数字作为值。超时参数设为0表示阻塞时间可以无限期延长(block indefinitely) 。

更多相关信息,请参考RPOPLPUSH命令。

时间复杂度:
O(1)
返回值:
假如在指定时间内没有任何元素被弹出,则返回一个 nil和等待时长。
反之,返回一个含有两个元素的列表,第一个元素是被弹出元素的值,第二个元素是等待时长。
# 情况1:非空列表

redis> BRPOPLPUSH msg reciver 500
"hello moto"    # 弹出元素的值
(3.38s)         # 等待时长

redis> LLEN reciver
(integer) 1

redis> LRANGE reciver 0 0
1) "hello moto"


# 情况2:空列表

redis> BRPOPLPUSH msg reciver 1
(nil)
(1.34s)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis中的链表实现是双向链表。每个节点包括一个指向前一个节点的指针(prev)和一个指向后一个节点的指针(next)。这种双向链表的结构使得在插入、删除和访问节点时具有高效性能。 Redis链表的定义如下: ```c typedef struct listNode { struct listNode *prev; struct listNode *next; void *value; } listNode; typedef struct list { listNode *head; listNode *tail; unsigned long len; void *(*dup)(void *ptr); void (*free)(void *ptr); int (*match)(void *ptr, void *key); } list; ``` 链表通过head指针指向第一个节点,通过tail指针指向最后一个节点。len链表的长度。 在Redis中,链表常用于实现列(list)数据结构,并提供了丰富的操作函数,如插入、删除、遍历等。 例如,向链表中插入一个新节点的操作可以通过以下代码实现: ```c listNode *listInsertNode(list *list, listNode *old_node, void *value, int after) { listNode *node = malloc(sizeof(*node)); if (node == NULL) return NULL; node->value = value; if (after) { node->prev = old_node; node->next = old_node->next; if (list->tail == old_node) list->tail = node; } else { node->next = old_node; node->prev = old_node->prev; if (list->head == old_node) list->head = node; } if (node->prev != NULL) node->prev->next = node; if (node->next != NULL) node->next->prev = node; list->len++; return node; } ``` 这段代码会在old_node节点之前或之后插入一个新节点,并更新相应的指针和链表长度。 除了基本的链表操作外,Redis链表还支持索引操作、范围操作等高级功能,使得链表更加灵活和强大。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值