spark算子_Spark常用算子

Spark的算子分类:

从大方向说,Spark算子大致可以分为以下两类:

(1)Transformation变换/转换算子:这种变换并不触发提交作业,这种算子是延迟执行的,也就是说从一个RDD转换生成另一个RDD的转换操作不是马上执行,需要等到有Action操作的时候才会真正触发。

(2)Action行动算子:这类算子会触发SparkContext提交job作业,并将数据输出到Spark系统。

从小方向说,Spark算子大致可以分为以下三类:

(1)Value数据类型的Transformation算子,这种变换并不触发提交作业,针对处理的数据项是Value型的数据

(2)Key-Value 数据类型的Transformation算子,这种变换并不触发提交作业,针对处理的数据项是Key-Value型的数据对

(3)Action算子:这类算子会触发SparkContext提交Job作业

(一)Value数据类型的Transformation算子:

(1)输入分区与输出分区一对一型:

1.map算子

2.flatMap算子

3.mapPartitions算子

4.mapPartitionsWithIndex算子

(2)输入分区与输出分区多对一型

5.union算子

6.cartesian算子

(3)输入分区与输出分区多对多型

7.groupBy算子、groupByKey算子

(4)输出分区是输入分区子集类型

8.filter算子

9.distinct算子

10.subtract算子

11.sample算子

(5)Cache算子

13.cache算子

14.persist算子

(二)Key-Value数据类型的Transformation算子

(1)输入分区与输出分区一对一

15.mapValues算子

(2)对单个RDD或者两个RDD聚集

单个RDD聚集

16.combineByKey算子

17.reduceByKey算子

18.repartition算子

两个RDD聚集

19.cogroup算子

(3)连接

20.join算子

21.leftOutJoin和rightOutJoin算子、fullOuterJoin算子

(三)Action算子

(1)无输出

22.foreach算子

(2)HDFS

23.saveAsTextFile算子

24.saveAsObjectFile算子

(3)Scala集合和数据类型

25.collect算子

26.collectAsMap算子

27.count,countByKey,CountByValue算子

28.take、takeSample算子

29.reduce算子

30.aggregate算子

31.zip、zipWithIndex算子

Transformation:

1.map算子

处理数据是一对一的关系,进入一条数据,出去的还是一条数据。map的输入变换函数应用于RDD中所有的元素,而mapPartitions应用于所有分区。区别于mapPartitions主要在于调度粒度不同。如parallelize(1 to 10 ,3),map函数执行了10次,而mapPartitions函数执行了3次。

val infos: RDD[String] = sc.parallelize(Array[String]("hello spark","hello hdfs","hello HBase"))
val result: RDD[Array[String]] = infos.map(one => {
  one.split(" ")
})
result.foreach(arr =>{arr.foreach(println)})

执行结果:

63358796a1c74a2f56c9424fafaf22ff.png

2.flatMap算子

flatMap是一对多的关系,处理一条数据得到多条结果

将原来 RDD 中的每个元素通过函数 f 转换为新的元素,并将生成的 RDD 的每个集合中的元素合并为一个集合。

f9cb1a4ae87dfd8c326a854da097a7a1.png
val infos: RDD[String] = sc.makeRDD(Array[String]("hello spark","hello hdfs","hello MapReduce"))
val rdd1: RDD[String] = infos.flatMap(one => {
  one.split(" ")
})
rdd1.foreach(println)

3.mapPartitions算子

mapPartitions遍历的是每一个分区中的数据,一个个分区的遍历。获 取 到 每 个 分 区 的 迭 代器,在 函 数 中 通 过 这 个 分 区 整 体 的 迭 代 器 对整 个 分 区 的 元 素 进 行 操 作,相对于map一条条处理数据,性能比较高,可获取返回值。

可以通过函数f(iter) =>iter.filter(_>=3)对分区中所有的数据进行过滤,大于和等于3的数据保留,一个方块代表一个RDD分区,含有1,2,3的分区过滤,只剩下元素3。

7c4b162686cfd40725d8d247dc4e7b11.png

4.mapPartitionsWithIndex(function)算子

拿到每个RDD中的分区,以及分区中的数据

 val lines: RDD[String] = sc.textFile("./data/words",5)
    val result: RDD[String] = lines.mapPartitionsWithIndex((index, iter) => {
      val arr: ArrayBuffer[String] = ArrayBuffer[String]()
      iter.foreach(one => {
//        one.split(" ")
        arr.append(s"partition = [$index] ,value = $one")
      })
      arr.iterator
    }, true)
    result.foreach(println)

e129f50937af6f3be32baf0bc0d63b2a.png

5.union算子

union合并两个RDD,两个RDD必须是同种类型,不一定是K,V格式的RDD

val rdd1: RDD[String] = sc.parallelize(List[String]("zhangsan","lisi","wangwu","maliu"),3)
val rdd2: RDD[String] = sc.parallelize(List[String]("a","b","c","d"),4)
val unionRDD: RDD[String] = rdd1.union(rdd2)
unionRDD.foreach(println)

6.cartesian算子

求笛卡尔积,该操作不会执行shuffle操作,但最好别用,容易触发OOM

7.groupBy算子

按照指定的规则,将数据分组

val rdd: RDD[(String, Double)] = sc.parallelize(List[(String,Double)](("zhangsan",66.5),("lisi",33.2),("zhangsan",66.7),("lisi",33.4),("zhangsan",66.8),("wangwu",29.8)))
val result: RDD[(Boolean, Iterable[(String, Double)])] = rdd.groupBy(one => {
  one._2 > 34
})
result.foreach(println)

07d94332813218af4e39e74c983d7cfa.png

groupByKey算子

根据key去将相同的key对应的value合并在一起(K,V)=>(K,[V])

val rdd: RDD[(String, Double)] = sc.parallelize(List[(String,Double)](("zhangsan",66.5),("lisi",33.2),("zhangsan",66.7),("lisi",33.4),("zhangsan",66.8),("wangwu",29.8)))
val rdd1: RDD[(String, Iterable[Double])] = rdd.groupByKey()
rdd1.foreach(info=>{
  val name: String = info._1
  val value: Iterable[Double] = info._2
  val list: List[Double] = info._2.toList
  println("name = "+name+",value ="+list)
})

f08049c75dcb0018d8451484fa7ef0ba.png

8.filter算子

过滤数据,返回true的数据会被留下

val infos: RDD[String] = sc.makeRDD(List[String]("hehe","hahha","zhangsan","lisi","wangwu"))
val result: RDD[String] = infos.filter(one => {
  !one.equals("zhangsan")
})
result.foreach(println)

2a021489ec7f172a4d86f74dfc419a8f.png

9.distinct算子

distinct去重,有shuffle产生,内部实际是map+reduceByKey+map实现

val infos: RDD[String] = sc.parallelize(List[String]("a","a","b","a","b","c","c","d"),4)
val result: RDD[String] = infos.distinct()
result.foreach(println)

215bf8952c24f04a329f8b73733185a6.png

10.subtract算子

取RDD的差集,subtract两个RDD的类型要一致,结果RDD的分区数与subtract算子前面的RDD分区数多的一致。

val rdd1 = sc.parallelize(List[String]("zhangsan","lisi","wangwu"),5)
val rdd2 = sc.parallelize(List[String]("zhangsan","lisi","maliu"),4)
val subtractRDD: RDD[String] = rdd1.subtract(rdd2)
subtractRDD.foreach(println)
println("subtractRDD partition length = "+subtractRDD.getNumPartitions)

83d6bddbdf0ba417f98009f4c25798a9.png

11.sample算子

sample随机抽样,参数sample(withReplacement:有无放回抽样,fraction:抽样的比例,seed:用于指定的随机数生成器的种子)

有种子和无种子的区别:

有种子是只要针对数据源一样,都是指定相同的参数,那么每次抽样到的数据都是一样的

没有种子是针对同一个数据源,每次抽样都是随机抽样

8ebf886314c84b51c9a6670f71ddd505.png

b97f958d58ff6061d0b372128f9806fe.png

(12.13)cache算子、persist算子

04c5612350b7070509837d03e29ef815.png
package core.persist

import org.apache.spark.rdd.RDD
import org.apache.spark.storage.StorageLevel
import org.apache.spark.{SparkConf, SparkContext}

/**
  * cache()和persist()注意问题
  * 1.cache()和persist()持久化单位是partition,cache()和persist()是懒执行算子,需要action算子触发执行
  * 2.对一个RDD使用cache或者persist之后可以赋值给一个变量,下次直接使用这个变量就是使用持久化的数据。
  * 也可以直接对RDD进行cache或者persist,不赋值给一个变量
  * 3.如果采用第二种方法赋值给变量的话,后面不能紧跟action算子
  * 4.cache()和persist()的数据在当前application执行完成之后会自动清除
  */
object CacheAndPersist {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("cache")
    val sc: SparkContext = new SparkContext(conf)
    sc.setLogLevel("Error")
    val lines: RDD[String] = sc.textFile("./data/persistData.txt")

    //    val linescache: RDD[String] = lines.persist(StorageLevel.MEMORY_ONLY)
    val linescache: RDD[String] = lines.cache()

    val startTime1: Long = System.currentTimeMillis()
    val count1: Long = linescache.count()
    val endTime1: Long = System.currentTimeMillis()
    println("count1 = "+count1+". time = "+(endTime1-startTime1) + "mm")

    val starttime2: Long = System.currentTimeMillis()
    val count2: Long = linescache.count()
    val endTime2: Long = System.currentTimeMillis()
    println("count2 = "+count2+", time = "+(endTime2-starttime2) + "ms")
    sc.stop()
  }
}

14.mapValues算子

针对K,V格式的数据,只对Value做操作,Key保持不变

val infos: RDD[(String, String)] = sc.makeRDD(
    List[(String,String)](
    ("zhangsan","18"),("lisi","20"),("wangwu","30")
    ))
val result: RDD[(String, String)] = infos.mapValues(s => {
  s + " " + "zhangsan18"
})
result.foreach(println)
sc.stop()

05660c893a0167fcb74c7347c792e3a1.png

15.flatMapValues算子

(K,V)->(K,V),作用在K,V格式的RDD上,对一个Key的一个Value返回多个Value

val infos: RDD[(String, String)] = sc.makeRDD(
    List[(String,String)](
    ("zhangsan","18"),("lisi","20"),("wangwu","30")
    ))
    val transInfo: RDD[(String, String)] = infos.mapValues(s => {
      s + " " + "zhangsan18"
    })
//    transInfo.foreach(println)
    val result: RDD[(String, String)] = transInfo.flatMapValues(s => {
      //按空格切分
      s.split(" ")
    })
    result.foreach(println)
    sc.stop()

7d824d5a8b92dbb2a380f0cd8e4e1cf3.png

16.combineByKey算子

首先给RDD中每个分区中的每一个key一个初始值

其次在RDD每个分区内部相同的key聚合一次

再次在RDD不同的分区之间将相同的key结果聚合一次

val rdd1: RDD[(String, Int)] = sc.makeRDD(List[(String, Int)](
  ("zhangsan", 10), ("zhangsan", 20), ("wangwu", 30),
  ("lisi", 40), ("zhangsan", 50), ("lisi", 60),
  ("wangwu", 70), ("wangwu", 80), ("lisi", 90)
),3)
rdd1.mapPartitionsWithIndex((index,iter)=>{
  val arr: ArrayBuffer[(String, Int)] = ArrayBuffer[(String,Int)]()
  iter.foreach(tp=>{
    arr.append(tp)
    println("rdd1 partition index ="+index+".value ="+tp)
  })
  arr.iterator
}).count()
println("++++++++++++++++++++++++++++++++++++")
val result: RDD[(String, String)] = rdd1.combineByKey(v=>{v+"hello"}, (s:String, v)=>{s+"@"+v}, (s1:String, s2:String)=>{s1+"#"+s2})
result.foreach(println)

fdbaf08495f4ef12cc459b09d3934b69.png

17.reduceByKey算子

首先会根据key去分组,然后在每一组中将value聚合,作用在KV格式的RDD上

首先会根据key去分组,然后在每一组中将value聚合,作用在KV格式的RDD上
val infos: RDD[(String, Int)] = sc.parallelize(
    List[(String,Int)](("zhangsan",1),("zhangsan",2),
    ("zhangsan",3),("lisi",100),("lisi",200)),5
    )
val result: RDD[(String, Int)] = infos.reduceByKey((v1, v2)=>{v1+v2})
result.foreach(println)
sc.stop()

ab7f817847aa071ecf13472770996f9d.png

18.repartition算子

重新分区,可以将RDD的分区增多或者减少,会产生shuffle,coalesc(num,true) = repartition(num)

val rdd1: RDD[String] = sc.parallelize(List[String](
  "love1", "love2", "love3", "love4",
  "love5", "love6", "love7", "love8",
  "love9", "love10", "love11", "love12"
), 3)
val rdd2: RDD[String] = rdd1.mapPartitionsWithIndex((index, iter) => {
  val list: ListBuffer[String] = ListBuffer[String]()
  iter.foreach(one => {
    list.append(s"rdd1 partition = [$index] ,value = [$one]")
  })
  list.iterator
}, true)
val rdd3: RDD[String] = rdd2.repartition(3)
val rdd4: RDD[String] = rdd3.mapPartitionsWithIndex((index, iter) => {
  val arr: ArrayBuffer[String] = ArrayBuffer[String]()
  iter.foreach(one => {
    arr.append(s"rdd3 partition = [$index] ,value = [$one]")
  })
  arr.iterator
})
val results: Array[String] = rdd4.collect()
results.foreach(println)
sc.stop()

054acb95e8df526bbbaac46fbe4a560d.png

19.cogroup算子

合并两个RDD,生成一个新的RDD。分区数与分区数多个那个RDD保持一致

val rdd1 = sc.parallelize(List[(String,String)](("zhangsan","female"),("zhangsan","female1"),("lisi","male"),("wangwu","female"),("maliu","male")),3)
val rdd2 = sc.parallelize(List[(String,Int)](("zhangsan",18),("lisi",19),("lisi",190),("wangwu",20),("tianqi",21)),4)
val resultRDD: RDD[(String, (Iterable[String], Iterable[Int]))] = rdd1.cogroup(rdd2)

resultRDD.foreach(info=>{
  val key = info._1
  val value1: List[String] = info._2._1.toList
  val value2: List[Int] = info._2._2.toList
  println("key ="+key+",value"+value1+", value2 = "+value2)
})
println("resultRDD partition length ="+resultRDD.getNumPartitions)
sc.stop()

3202c3cc55b4b1834835c30da13d3964.png

20.join算子

会产生shuffle,(K,V)格式的RDD和(K,V)格式的RDD按照相同的K,join得到(K,(V,W))格式的数据,分区数按照大的来。

val nameRDD: RDD[(String, String)] = sc.parallelize(List[(String,String)](("zhangsan","female"),("lisi","male"),("wangwu","female")),3)
val scoreRDD: RDD[(String, Int)] = sc.parallelize(List[(String,Int)](("zhangsan",18),("lisi",19),("wangwu",20)),2)
val joinRDD: RDD[(String, (String, Int))] = nameRDD.join(scoreRDD)
println(joinRDD.getNumPartitions)
joinRDD.foreach(println)

68ee3df223b15ee613a7035df25f2b38.png

21.leftOutJoin、rightOutJoin算子、fullOuterJoin算子

leftOuterJoin(K,V)格式的RDD和(K,V)格式的RDD,使用leftOuterJoin结合,以左边的RDD出现的key为主 ,得到(K,(V,Option(W)))

val nameRDD: RDD[(String, String)] = sc.parallelize(
    List[(String,String)](("zhangsan","female"),
    ("lisi","male"),("wangwu","female"),("maliu","male")
    ))
val scoreRDD: RDD[(String, Int)] = sc.parallelize(
    List[(String,Int)](("zhangsan",22),("lisi",19),
    ("wangwu",20),("tianqi",21)
    ))
val leftOutJoin: RDD[(String, (String, Option[Int]))] = nameRDD.leftOuterJoin(scoreRDD)
leftOutJoin.foreach(println)
sc.stop()

7fab1a1bf687373f472bec0a11ec0515.png

rightOuterJoin(K,V)格式的RDD和(K,W)格式的RDD使用rightOuterJoin结合以右边的RDD出现的key为主,得到(K,(Option(V),W))

val nameRDD: RDD[(String, String)] = sc.parallelize(
    List[(String,String)](("zhangsan","female"),("lisi","male")
    ,("wangwu","female"),("maliu","male")),3
    )
  val scoreRDD: RDD[(String, Int)] = sc.parallelize(
      List[(String,Int)](("zhangsan",18),("lisi",19),
      ("wangwu",20),("tianqi",21)),4
      )
  val rightOuterJoin: RDD[(String, (Option[String], Int))] = nameRDD.rightOuterJoin(scoreRDD)
  rightOuterJoin.foreach(println)
  println("rightOuterJoin RDD partiotion length = "+rightOuterJoin.getNumPartitions)
  sc.stop()

dee0a3bc94d67dd4efe89e91ea274522.png

fullOuterJoin算子(K,,V)格式的RDD和(K,V)格式的RDD,使用fullOuterJoin结合是以两边的RDD出现的key为主,得到(K(Option(V),Option(W)))

val nameRDD: RDD[(String, String)] = sc.parallelize(List[(String,String)](("zhangsan","female"),("lisi","male"),("wangwu","female"),("maliu","male")),3)
val ageRDD: RDD[(String, Int)] = sc.parallelize(List[(String,Int)](("zhangsan",18),("lisi",16),("wangwu",20),("tianqi",21)),4)
val fullOuterJoin: RDD[(String, (Option[String], Option[Int]))] = nameRDD.fullOuterJoin(ageRDD)
fullOuterJoin.foreach(println)
println("fullOuterJoin RDD partition length = "+fullOuterJoin.getNumPartitions)
sc.stop()

5e33632418140ea991046711fdc9e51e.png

22.intersection算子

intersection取两个RDD的交集,两个RDD的类型要一致,结果RDD的分区数要与两个父RDD多的那个一致

val rdd1: RDD[String] = sc.parallelize(List[String]("zhangsan","lisi","wangwu"),5)
val rdd2: RDD[String] = sc.parallelize(List[String]("zhangsan","lisi","maliu"),4)
val result: RDD[String] = rdd1.intersection(rdd2)
result.foreach(println)
println("intersection partition length = "+ result.getNumPartitions)
sc.stop()

1a2e1a2ee234cc8f49e3ba6eba3cdbdd.png

23.foreach算子

foreach遍历RDD中的每一个元素

val lines: RDD[String] = sc.textFile("./data/words") lines.foreach(println)

24.saveAsTextFile算子

将DataSet中的元素以文本的形式写入本地文件系统或者HDFS中,Spark将会对每个元素调用toString方法,将数据元素转换成文本文件中的一行数据,若将文件保存在本地文件系统,那么只会保存在executor所在机器的本地目录

val infos: RDD[String] = sc.parallelize(List[String]("a","b","c","e","f","g"),4)

infos.saveAsTextFile("./data/infos")

0fc73308ac5b6616f2ab5142774e8ac4.png

70b75e16e5eb2895978e3af4705df36f.png

25.saveAsObjectFile算子

将数据集中元素以ObjectFile形式写入本地文件系统或者HDFS中

infos.saveAsObjectFile("./data/infosObject")

eb0e9694878193e1b1fa485c78cf2423.png

26.collect算子

collect回收算子,会将结果回收到Driver端,如果结果比较大,就不要回收,这样的话会造成Driver端的OOM

val lines: RDD[String] = sc.textFile("./data/words")
sc.setLogLevel("Error")
val result: Array[String] = lines.collect()
result.foreach(println)

1dff4ec282c3bd33f995e6ec4ea0c503.png

27.collectAsMap算子

将K、V格式的RDD回收到Driver端作为Map使用

val weightInfos: RDD[(String, Double)] = sc.parallelize(
    List[(String,Double)](new Tuple2("zhangsan",99),
        new Tuple2("lisi",78.6),
        new Tuple2("wangwu",122.2323)
        )
        )
val stringToDouble: collection.Map[String, Double] = weightInfos.collectAsMap()
stringToDouble.foreach(tp=>{
  println(tp._1+"**************"+tp._2)
})
sc.stop()

573a46d9ea428d6195747a8df22c2e63.png

28.count算子

count统计RDD共有多少行数据

val lines: RDD[String] = sc.textFile("./data/sampleData.txt")

val result: Long = lines.count()

println(result)

sc.stop()

直接给出结果行数

29.countByKey算子、countByValue算子

countByKey统计相同的key出现的个数

val rdd: RDD[(String, Integer)] = sc.makeRDD(List[(String,Integer)](
    ("a",1),("a",100),("a",1000),("b",2),("b",200),("c",3),("c",4),("d",122)
    ))
val result: collection.Map[String, Long] = rdd.countByKey()
result.foreach(println)

752b2bc10d544c74b227b16e223648cd.png

countByValue统计RDD中相同的Value出现的次数,不要求数据必须为RDD格式

val rdd = sc.makeRDD(List[(String,Integer)](
    ("a",1),("a",1),("a",1000),("b",2),("b",200),("c",3),("c",3)
    ))
val result: collection.Map[(String, Integer), Long] = rdd.countByValue()
result.foreach(println)

b65bb5623a69b5727b485cda2e88e909.png

30、take、takeSample算子

take取出RDD中的前N个元素

val lines: RDD[String] = sc.textFile("./data/words")

val array: Array[String] = lines.take(3)

array.foreach(println)

takeSapmle(withReplacement,num,seed),随机抽样将数据结果拿回Driver端使用,返回Array,

withReplacement:有无放回抽样,num:抽样的条数,seed:种子

val lines: RDD[String] = sc.textFile("./data/words")

val result: Array[String] = lines.takeSample(false,3,10)

result.foreach(println)

3f104c325ea92ac9c2df0e6046dab19e.png

31、reduce算子

val rdd: RDD[Int] = sc.makeRDD(Array[Int](1,2,3,4,5))

val result: Int = rdd.reduce((v1, v2) => { v1 + v2 })

//直接得到结果

println(result) }

32.Aggregate算子----transformation类算子

首先是给定RDD的每一个分区一个初始值,然后RDD中每一个分区中按照相同的key,结合初始值去合并,最后RDD之间相同的key聚合

val rdd1: RDD[(String, Int)] = sc.makeRDD(List[(String, Int)](
  ("zhangsan", 10), ("zhangsan", 20), ("wangwu", 30),
  ("lisi", 40), ("zhangsan", 50), ("lisi", 60),
  ("wangwu", 70), ("wangwu", 80), ("lisi", 90)
), 3)
rdd1.mapPartitionsWithIndex((index,iter)=>{
  val arr: ArrayBuffer[(String, Int)] = ArrayBuffer[(String,Int)]()
  iter.foreach(tp=>{
    arr.append(tp)
    println("rdd1 partition index ="+index+", value ="+tp)
  })
  arr.iterator
}).count()
val result: RDD[(String, String)] = 
    rdd1.aggregateByKey("hello")(
        (s, v)=>{s+"~"+v}, (s1, s2)=>{s1+"#"+s2}
        )
result.foreach(println)

1a0bfa520071f9678a0665095d726191.png

mapPartitionsWithIndex注释掉执行结果:

72eef219e609a94792e370d12de05517.png

33.zip算子 ---Transformation类算子

将两个RDD合成一个K,V格式的RDD,分区数要相同,每个分区中的元素必须相同

val rdd1: RDD[String] = sc.parallelize(List[String]("a","b","c","d"),2)
val rdd2: RDD[Int] = sc.parallelize(List[Int](1,2,3,4),2)
val result: RDD[(String, Int)] = rdd1.zip(rdd2)
result.foreach(println)

8cc21481c1a637663fd25dd458c3e08a.png

33、zipWithIndex算子---Transformation类算子

val rdd1 = sc.parallelize(List[String]("a","b","c"),2)
val rdd2 = sc.parallelize(List[Int](1,2,3),numSlices = 2)
val result: RDD[(String, Long)] = rdd1.zipWithIndex()
val result2: RDD[(Int, Long)] = rdd2.zipWithIndex()
result.foreach(println)
result2.foreach(println)

dc137156feb43b72c7f34a845271d06a.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值