8:map,flatMap,reduce,flod,scan,zip,iterator,stream,view,par,match

第十一章 数据结构(下)-集合操作

11.1 集合元素的映射-map

看一个实际需求
  要求:请将 List(3, 5, 7) 中的所有元素都 * 2,将其结果放到一个新的集合中返回,即返回一个新的 List(6, 10, 14), 请编写程序实现。
使用传统的方法解决
示例代码如下:

package com.atguigu.chapter11.test

/**
  * 要求:请将 List(3, 5, 7) 中的所有元素都 * 2,将其结果放到一个新的集合中返回,即返回一个新的 List(6, 10, 14), 请编写程序实现。
  */
object MapOperateDemo01 {
  def main(args: Array[String]): Unit = {
    val list1 = List(3, 5, 7) // 集合
    var list2 = List[Int]() // 新的集合,准备放入新的内容
    for (item <- list1) { // 遍历
      list2 = list2 :+ item * 2 // 对元素*2,然后加入list2集合
    }
    println("list2=" + list2) // List(6, 10, 14)

    // 对上面传统的方法来解决问题的小结:
    // 1. 优点
    //(1) 处理方法比较直接,好理解
    // 2. 缺点
    // (1) 不够简洁,高效
    // (2) 没有体现函数式编程特点:集合 => 函数 => 新的集合 => 函数 ..
    // (3) 不利于处理复杂的数据处理业务
  }
}

输出结果如下:

list2=List(6, 10, 14)

11.1.1 map 映射函数的操作


内存图解如下:

11.1.2 高阶函数基本使用案例1+案例2

示例代码如下:

package com.atguigu.chapter11.test

object HighOrderFunDemo01 {
  def main(args: Array[String]): Unit = {
    // 简言之:二阶带参函数
    // 使用高阶函数(函数中有函数)=> 函数中有另一个函数的名称
    // val res1 = test1(sum, 3.5) // 等价于下面形式
    val res1 = test1(sum _, 3.5)
    println("res1=" + res1)

    // 在 scala 中,可以把一个函数直接赋给一个变量,但是不执行函数,格式:函数名 _   注意:本质上是将内存地址赋值给栈里面的变量!!!
    val f0 = myPrint1 // 赋值的同时执行无参函数
    val f1 = myPrint1 _
    f1() // 执行无参函数
    val f2 = myPrint2 _
    f2(100) // 执行带参函数
    // 注意:对于一阶函数而言,函数名 或者 函数名 _ 效果是不一样的。

    // 简言之:二阶不带参函数
    // test2(sayOK) // 等价于下面形式
    test2(sayOK _)
    // 注意:对于高阶函数而言,函数(函数名) 或者 函数(函数名 _) 效果是一样的。
  }

  // 简言之:一阶函数
  def myPrint1(): Unit = {
    println("hello world")
  }

  def myPrint2(i: Int): Unit = {
    println("hello world" + i)
  }

  // 说明
  // 1. test1 就是一个高阶带参函数
  // 2. f: Double => Double 表示一个函数,该函数可以接受一个 Double,返回 Double
  // 3. n1: Double 普通参数
  // 4. f(n1) 在 test1 函数中,执行 你传入的函数
  def test1(f: Double => Double, n1: Double) = {
    f(n1)
  }

  // 普通的函数,可以接受一个 Double,返回 Double
  def sum(d: Double): Double = {
    d + d
  }

  def test2(f: () => Unit) = {
    f()
  }

  def sayOK() = {
    println("sayOK")
  }
}

输出结果如下:

res1=7.0
hello world
hello world
hello world100
sayOK

11.1.3 使用 map 映射函数来解决

示例代码如下:

package com.atguigu.chapter11.test

object MapOperateDemo02 {
  def main(args: Array[String]): Unit = {
    // 说明 list1.map(f1) 做了什么
    // 1. 将 List 这个集合的元素依次遍历
    // 2. 将各个元素传递给 f1 函数 => 新 Int
    // 3. 将得到新 Int,放入到一个新的集合并返回
    // 4. 因此 f1 函数会被调用3次
    val list1 = List(3, 5, 7)
    val list2 = list1.map(f1)
    println(list2) // List(6, 10, 14)
  }

  def f1(n: Int): Int = {
    2 * n
  }

}

输出结果如下:

List(6, 10, 14)

11.1.4 模拟实现 map 映射函数的机制

示例代码如下:

package com.atguigu.chapter11.test

object MapOperateDemo02 {
  def main(args: Array[String]): Unit = {
    // 说明 list1.map(f1) 做了什么
    // 1. 将 List 这个集合的元素依次遍历
    // 2. 将各个元素传递给 f1 函数 => 新 Int
    // 3. 将得到新 Int,放入到一个新的集合并返回
    // 4. 因此 f1 函数会被调用3次
    val list1 = List(3, 5, 7)
    val list2 = list1.map(f1)
    println(list2) // List(6, 10, 14)

    // 模拟实现 map 映射函数的机制(固定参数个数)
    val myList1 = MyList()
    val myList2 = myList1.map(f1)
    println(myList2) // List(6, 10, 14)

    // 模拟实现 map 映射函数的机制(可变参数)
    val myList01 = new MyList01(1, 2, 3, 4)
    val myList02 = myList01.map(f1)
    println(myList02)
  }

  def f1(n: Int): Int = {
    2 * n
  }
}

// 自定义 List 集合
class MyList {
  var list1 = List(3, 5, 7)
  var list2 = List[Int]()

  def map(f: Int => Int): List[Int] = {
    for (item <- list1) {
      // 过滤
      // 扁平化
      // ......
      // 遍历
      list2 = list2 :+ f(item)
    }
    list2
  }
}

// 伴生对象,重写 apply 方法
object MyList {
  def apply(): MyList = new MyList()
}

class MyList01(params: Int*) {
  var list02 = List[Int]()

  def map(f: Int => Int): List[Int] = {
    for (item <- params) {
      // 过滤
      // 扁平化
      // ......
      // 遍历
      list02 = list02 :+ f(item)
    }
    list02
  }
}

输出结果如下:

List(6, 10, 14)
List(6, 10, 14)
List(2, 4, 6, 8)

11.1.5 课堂练习

  请将 val names = List("Alice", "Bob", "Nick") 中的所有单词,全部转成字母大写,返回到新的 List 集合中。
示例代码如下:

package com.atguigu.chapter11.exercise

/**
  * 请将 `val names = List("Alice", "Bob", "Nick")` 中的所有单词,全部转成字母大写,返回到新的 List 集合中。
  */
object MapExercise01 {
  def main(args: Array[String]): Unit = {
    val names = List("Alice", "Bob", "Nick")

    def upper(s: String): String = {
      s.toUpperCase
    }

    val names2 = names.map(upper)
    println("names=" + names2) // names=List(ALICE, BOB, NICK)
  }
}

输出结果如下:

names=List(ALICE, BOB, NICK)

11.2 集合元素的扁平-flatMap

  flat 即压扁,压平,扁平化,效果就是将集合中的每个元素的子元素映射到某个函数并返回新的集合。
示例代码如下:

package com.atguigu.chapter11.test

object FlatMapDemo01 {
  def main(args: Array[String]): Unit = {
    val names = List("Alice", "Bob", "Nick")

    def upper( s : String ) : String = {
      s. toUpperCase
    }

    // 注意:每个字符串也是 char 集合
    println(names.flatMap(upper)) // List(A, L, I, C, E, B, O, B, N, I, C, K)
  }
}

输出结果如下:

List(A, L, I, C, E, B, O, B, N, I, C, K)

11.3 集合元素的过滤-filter

  集合元素的过滤 filter:将符合要求的数据(筛选)放置到新的集合中。
  应用案例:将 val names = List("Alice", "Bob", "Nick") 集合中首字母为'A'的筛选到新的集合。
  注意:集合.filter(过滤函数) 过滤函数的返回值类型必须是布尔类型。
示例代码如下:

package com.atguigu.chapter11.test

object FilterDemo01 {
  def main(args: Array[String]): Unit = {
    val names = List("Alice", "Bob", "Nick")

    def startA(s: String): Boolean = {
      s.startsWith("A")
    }

    val names2 = names.filter(startA)
    println("names=" + names2) // names=List(Alice)
  }
}

输出结果如下:

names=List(Alice)

11.4 集合元素的化简-reduce

化简介绍
  看一个需求:val list = List(1, 2, 3, 4 ,5),求出 list 的和。
  化简:将二元函数引用于集合中的函数。
  上面的问题当然可以使用遍历 list 方法来解决,这里我们使用 scala 的化简方式来完成。
示例代码如下:

package com.atguigu.chapter11.test

/**
  * val list = List(1, 20, 30, 4 ,5),求出 list 的和。
  */
object ReduceDemo01 {
  def main(args: Array[String]): Unit = {
    val list = List(1, 2, 3, 4, 5)

    def sum(n1: Int, n2: Int): Int = {
      n1 + n2
    }

    val res1 = list.reduceLeft(sum)   // ((((1+2)+3)+4)+5) = 15
    val res2 = list.reduceRight(sum)  // (1+(2+(3+(4+5)))) = 15
    println("res1=" + res1)
    println("res2=" + res2)

    // 说明
    // def reduceLeft[B >: A](f: (B, A) => B): B
    // reduceLeft(f) 接收的函数需要的形式为 f: (B, A) => B): B
    // reduceleft(f) 的运行规则是:从左边开始执行将得到的结果返回给第一个参数
    // 然后继续和下一个元素运行,将得到的结果继续返回给第一个参数,继续...
    // 即: ((((1+2)+3)+4)+5) = 15
  }
}

输出结果如下:

res1=15
res2=15

课堂练习
示例代码如下:

package com.atguigu.chapter11.exercise

object ReduceExercise01 {
  def main(args: Array[String]): Unit = {
    // 1、分析下面的代码输出什么结果
    val list = List(1, 2, 3, 4, 5)

    def minus(num1: Int, num2: Int): Int = {
      num1 - num2
    }

    println(list.reduceLeft(minus)) // -13
    println(list.reduceRight(minus)) // 3 = (1-(2-(3-(4-5))))
    println(list.reduce(minus)) // -13 是哪个,看源码秒懂

    // 2、使用化简的方法求出 List(3, 4, 2, 7, 5) 最小的值
    val list1 = List(3, 4, 2, 7, 5)

    def min(n1: Int, n2: Int): Int = {
      if (n1 < n2) n1 else n2
    }

    println(list1.reduceLeft(min))
  }
}

输出结果如下:

-13
3
-13
2

11.5 集合元素的折叠-fold

scala中集合类iterator特质的化简和折叠方法 
c.reduceLeft(op)这样的调用将op相继应用到元素,如

 

val a = List(1,7,2,9)
val a1 = a.reduceLeft(_ - _)//      ((1-7) - 2) - 9 = -17

 

 

c.foldLeft(0)(_ * _)方法 

    val a2 = a.foldLeft(0)(_ - _) //    0-1-7-2-9 = -19

对于foldLeft方法还有一种简写,这种写法的本意是让你通过/:来联想一棵树的样子 
对/:操作符来说,初始值是第一个操作元本题中是0,:后是第二个操作元a

    val a3 = (0 /: a)(_ - _) //      等价于a.foldLeft(0)(_ - _)

scala同样也提供了foldRight或:\的变体,计算 

 val a4 = a.foldRight(0)(_ - _)//    1-(7-(2-(9-0))) = -13
 val a5 = (a :\ 0)(_ - _)   //    等价于a.foldRight(0)(_ - _)

 

好像上面的方法都没什么用,但折叠fold有时候可以代替循环,例如: 
统计每个字母中字符串出现的次数

object aiguigu {
  def main(args: Array[String]): Unit = {
    val tmp = "Mississippi"
    val frep = collection.mutable.Map[Char, Int]()
    for (c <- tmp)
      frep(c) = frep.getOrElse(c, 0) + 1
 
    val result = (Map[Char, Int]() /: tmp) // 使用 /: foldLeft()()来统计次数
    {
      (m, c) => m + (c -> (m.getOrElse(c, 0) + 1))
    }
 
    val result1 = tmp.foldLeft(Map[Char, Int]()) // 使用/: foldLeft()()来统计次数
    {
      (m, c) => m + (c -> (m.getOrElse(c, 0) + 1))
    }
    //      任何while循环都可用fold来代替
    println(frep, result, result1) //(Map(M -> 1, s -> 4, p -> 2, i -> 4),Map(M -> 1, i -> 4, s -> 4, p -> 2))
  }
}

 这是使用fold的步骤:在每一步,将频率映射和新遇到的字母结合在一起,产生一个新的频率映射。这就是折叠: 

fold的初始值和操作符是分开定义的柯里化参数,这样scala就可以根据类型来推断操作符的类型定义。 
eg:该函数的初始值是String,因此操作符的类型应该是(String,Int) => String的函数

    val a = List(1,7,2,9)
    val a7 = a.foldLeft("")(_ + _)
    val a8 = a.foldRight("")(_+_)
    val a9 = a.fold(0)(_+_)
    println(a7,a8,a9) //    (1729,1729,19)

折叠介绍


应用案例
示例代码如下:

package com.atguigu.chapter11.test

object FoldDemo01 {
  def main(args: Array[String]): Unit = {
    // 折叠
    val list = List(1, 2, 3, 4)

    def minus(num1: Int, num2: Int): Int = {
      num1 - num2
    }

    // 说明:折叠的理解和化简的运行机制几乎一样
    // 理解 list.foldLeft(5)(minus) 理解成 list(5, 1, 2, 3, 4) list.reduceLeft(minus)
    //步骤 (5-1)
    //步骤 ((5-1) - 2)
    //步骤 (((5-1) - 2) - 3)
    //步骤 ((((5-1) - 2) - 3)) - 4 = - 5
    println(list.foldLeft(5)(minus)) // -5  函数的柯里化现象:把一个函数的多个参数分散开来传递

    // 理解 list.foldRight(5)(minus) 理解成 list(1, 2, 3, 4, 5).reduceRight(minus)
    // 步骤 (4 - 5)
    // 步骤 (3- (4 - 5))
    // 步骤 (2 -(3- (4 - 5)))
    // 步骤 1- (2 -(3- (4 - 5))) = 3
    println(list.foldRight(5)(minus)) // 3
  }
}

输出结果如下:

-5
3

foldLeft 和 foldRight 缩写方法分别是:/: 和 :\
示例代码如下:

package com.atguigu.chapter11.test

object FoldDemo02 {
  def main(args: Array[String]): Unit = {
    val list4 = List(1, 9, 2, 8)

    def minus(num1: Int, num2: Int): Int = {
      num1 - num2
    }

    var i6 = (1 /: list4) (minus) // 等价于 list4.foldLeft(1)(minus)
    println(i6) // -19

    i6 = (100 /: list4) (minus) // 等价于 list4.foldLeft(100)(minus)
    println(i6) // 80

    i6 = (list4 :\ 10) (minus) //  等价于 list4.foldRight(10)(minus)
    println(i6) // -4
  }
}

输出结果如下:

-19
80
-4

11.6 集合元素的扫描-scan

扫描介绍
  扫描,即对某个集合的所有元素做 fold 操作,但是会把产生的所有中间结果放置于一个集合中保存。
应用实例
示例代码如下:

package com.atguigu.chapter11.test

object ScanDemo01 {
  def main(args: Array[String]): Unit = {

    def minus(num1: Int, num2: Int): Int = {
      num1 - num2
    }

    // 5 (1, 2, 3, 4, 5) => (5, 4, 2, -1, -5, -10)
    val i8 = (1 to 5).scanLeft(5)(minus) // IndexedSeq[Int]
    println(i8) // Vector(5, 4, 2, -1, -5, -10)

    def add(num1: Int, num2: Int): Int = {
      num1 + num2
    }

    // 5 (1, 2, 3, 4, 5) => (5, 6, 8, 11, 15, 20)
    val i9 = (1 to 5).scanLeft(5)(add) // IndexedSeq[Int]
    println(i9) // Vector(5, 6, 8, 11, 15, 20)

    // (1, 2, 3, 4, 5) 5 => (20, 19, 17, 14, 10, 5)
    val i10 = (1 to 5).scanRight(5)(add) // IndexedSeq[Int]
    println(i10) // Vector(20, 19, 17, 14, 10, 5)
  }
}

输出结果如下:

Vector(5, 4, 2, -1, -5, -10)
Vector(5, 6, 8, 11, 15, 20)
Vector(20, 19, 17, 14, 10, 5)

11.7 集合的综合应用案例

练习1:val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD" 将 sentence 中各个字符,通过 foldLeft 存放到一个 ArrayBuffer 中。
目的:理解 foldLeft 的用法
示例代码如下:

package com.atguigu.chapter11.exercise

import scala.collection.mutable.ArrayBuffer

/**
  * 练习1:val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD" 将 sentence 中各个字符,通过 foldLeft 存放到一个 ArrayBuffer 中。
  * 目的:理解 foldLeft 的用法
  */
object Exercise01 {
  def main(args: Array[String]): Unit = {
    val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD"

    def putArray(arrBuf: ArrayBuffer[Char], c: Char): ArrayBuffer[Char] = {
      arrBuf.append(c)
      arrBuf
    }

    val arrBuf = ArrayBuffer[Char]()
    // 将 sentence.foldLeft(arrBuf)(putArray) 理解成 sentence(arrBuf, A, A, ..., D, D).reduceLeft(putArray)
    println(sentence.foldLeft(arrBuf)(putArray))
    // foldLeft(B)(f) 的运行规则是:从左边开始执行将得到的结果返回给第一个参数(注意:此时这里的第一个参数是一个集合)
    // 然后继续和下一个元素运行,将得到的结果继续返回给第一个参数,继续...
  }
}

输出结果如下:

ArrayBuffer(A, A, A, A, A, A, A, A, A, A, B, B, B, B, B, B, B, B, C, C, C, C, C, D, D, D, D, D, D, D)

练习2:val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD" 使用映射集合,统计一句话中,各个字母出现的次数。
提示:Map[Char, Int]()
(1)使用 java 实现
示例代码如下:

package com.atguigu.chapter11.exercise;

import java.util.HashMap;
import java.util.Map;

public class JavaExercise02 {
    public static void main(String[] args) {
        String sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD";
        Map<Character, Integer> charCountMap = new HashMap<>();
        // 将字符串转化为字符数组
        char[] cs = sentence.toCharArray();
        // 遍历字符数组
        for (char c : cs) { // Map 中有该键,取出该键对应的值+1后重新存入
            if (charCountMap.containsKey(c)) {
                Integer count = charCountMap.get(c);
                charCountMap.put(c, count + 1);
            } else { // Map 中没有该键,以键值对的方式存入该键(该键对应的值为1)
                charCountMap.put(c, 1);
            }
        }
        System.out.println(charCountMap);
    }
}

输出结果如下:

{A=10, B=8, C=5, D=7}

(2)使用 scala 的 flodLeft 折叠方式实现
示例代码如下:

package com.atguigu.chapter11.exercise

import scala.collection.mutable

/**
  * 练习2:val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD" 使用映射集合,统计一句话中,各个字母出现的次数。
  * 提示:Map[Char, Int]()
  */
object Exercise02 {
  def main(args: Array[String]): Unit = {
    val sentence = "AAAAAAAAAABBBBBBBBCCCCCDDDDDDD"

    // 方式一:不可变 Map,是有序的
    def charCount1(map: Map[Char, Int], c: Char): Map[Char, Int] = {
      // 向 Map 中添加键值对
      // val map2 = map + (c -> (map.getOrElse(c, 0) + 1)) // 如果键c有返回键c对应的值并加1,键c没有返回0并加1
      // map2
      // 简写
      map + (c -> (map.getOrElse(c, 0) + 1)) // 如果键c有,返回键c对应的值并加1,键c没有,返回0并加1
    }

    val map1 = Map[Char, Int]()
    println(sentence.foldLeft(map1)(charCount1)) // Map(A -> 10, B -> 8, C -> 5, D -> 7)
    // 将 sentence.foldLeft(map1)(charCount) 理解成 sentence(map1, A, A, ..., D, D).reduceLeft(charCount)
    // foldLeft(B)(f) 的运行规则是:从左边开始执行将得到的结果返回给第一个参数(注意:此时这里的第一个参数是一个集合)
    // 然后继续和下一个元素运行,将得到的结果继续返回给第一个参数,继续...

    println("------------------------------------")

    // 方式二:可变 Map,是无序的
    def charCount2(map: mutable.Map[Char, Int], c: Char): mutable.Map[Char, Int] = {
      // 向 Map 中添加键值对
      map += (c -> (map.getOrElse(c, 0) + 1)) // 如果键c有,返回键c对应的值并加1,键c没有,返回0并加1
    }

    val map2 = mutable.Map[Char, Int]()
    println(sentence.foldLeft(map2)(charCount2))
  }
}

输出结果如下:

Map(A -> 10, B -> 8, C -> 5, D -> 7)
------------------------------------
Map(D -> 7, A -> 10, C -> 5, B -> 8)

练习3:大数据中经典的 wordcount 案例。
val lines = List("atguigu han hello ", "atguigu han aaa aaa aaa ccc ddd uuu")
使用映射集合,list 中各个单词出现的次数,并按出现次数排序。
示例代码链接:xxx

11.8 集合的合并-zip

  在开发中,当我们需要将两个集合进行 对偶元组合并,可以使用拉链
示例代码如下:

package com.atguigu.chapter11.test

object ZipDemo01 {
  def main(args: Array[String]): Unit = {
    // 拉链
    val list1 = List(1, 2, 3)
    val list2 = List(4, 5, 6)
    val list3 = list1.zip(list2)
    println(list3) // List((1,4), (2,5), (3,6))
  }
}

输出结果如下:

List((1,4), (2,5), (3,6))

拉链使用的注意事项

11.9 集合的迭代器-iterator

基本说明
  通过 iterator 方法从集合获得一个迭代器,通过 while 循环和 for 表达式对集合进行遍历。(学习使用迭代器进行集合的遍历)
示例代码如下:

package com.atguigu.chapter11.test

object IteratorDemo01 {
  def main(args: Array[String]): Unit = {
    val iterator = List(1, 2, 3, 4, 5).iterator // 得到迭代器
    /*
    这里我们看看 iterator 的继承关系
    def iterator: Iterator[A] = new AbstractIterator[A] {
      var these = self
      def hasNext: Boolean = !these.isEmpty
      def next(): A =
        if (hasNext) {
          val result = these.head; these = these.tail; result
        } else Iterator.empty.next()
     */
    println("----------遍历方式1 while ----------")
    while (iterator.hasNext) {
      println(iterator.next())
    }
    println("----------遍历方式2 for ----------")
    for (enum <- iterator) {
      println(enum)
    }

  }
}

输出结果如下:

----------遍历方式1 while ----------
1
2
3
4
5
----------遍历方式2 for ----------
1
2
3
4
5

应用案例小结

11.10 流-Stream

基本介绍


流的应用案例
示例代码如下:

package com.atguigu.chapter11.test

object StreamDemo01 {
  def main(args: Array[String]): Unit = {
    // 创建 Stream
    def numsForm(n: BigInt): Stream[BigInt] = n #:: numsForm(n + 1)

    val stream1 = numsForm(1)
    println(stream1) // Stream(1, ?)
    // 取出第一个元素
    println("head=" + stream1.head) // head=1
    // 使用 tail,会动态的向 stream 集合按规则生成新的元素
    println(stream1.tail) // Stream(2, ?)
    println(stream1) // Stream(1, 2, ?)

    // 案例:使用 map 映射 stream 的元素并进行一些计算
    //创建Stream
    def numsForm2(n: BigInt): Stream[BigInt] = n #:: numsForm2(n + 1)

    def multi(x: BigInt): BigInt = {
      x * x
    }

    println(numsForm2(5).map(multi)) // Stream(25, ?)
  }
}

输出结果如下:

Stream(1, ?)
head=1
Stream(2, ?)
Stream(1, 2, ?)
Stream(25, ?)

11.11 视图-view


示例代码如下:

package com.atguigu.chapter11.test

object ViewDemo01 {
  def main(args: Array[String]): Unit = {
    def multiple(num: Int): Int = {
      num
    }

    def eq(i: Int): Boolean = {
      i.toString.equals(i.toString.reverse)
    }

    // 没有使用 view
    val viewSquares1 = (1 to 100).map(multiple).filter(eq)
    println(viewSquares1)

    // 使用 view,view 方法产出一个总是被懒执行的集合
    val viewSquares2 = (1 to 100).view.map(multiple).filter(eq)
    println(viewSquares2)
    // 遍历
    for (item <- viewSquares2) {
      println(item)
    }
    // 小结:
    // 对集合进行 map, filter, reduce, fold, ...
    // 你并不希望立即执行,而是在使用到结果时才执行,则可以使用 view 来进行优化(大数据优化)
  }
}

输出结果如下:

Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99)
SeqViewMF(...)
1
2
3
4
5
6
7
8
9
11
22
33
44
55
66
77
88
99

11.12 线程安全的集合

11.13 并行集合


示例代码如下:
1、打印1~5

package com.atguigu.chapter11.test

object ParallelDemo01 {
  def main(args: Array[String]): Unit = {
    // (1 to 5).foreach(println) 等价于下面
    (1 to 5).foreach(println(_))

    println("----------")

    // 这里输出的结果是无序的,说明是将 println 任务分配给不同 cpu
    (1 to 5).par.foreach(println(_))
  }
}

输出结果如下:

1
2
3
4
5
----------
1
3
5
2
4

2、查看并行集合中元素访问的线程
示例代码如下:

package com.atguigu.chapter11.test

object ParallelDemo02 {
  def main(args: Array[String]): Unit = {
    val result1 = (0 to 100).map{case _ => Thread.currentThread.getName}.distinct
    val result2 = (0 to 100).par.map{case _ => Thread.currentThread.getName}.distinct
    println(result1)
    println(result2)
  }
}

输出结果如下:

Vector(main)
ParVector(ForkJoinPool-1-worker-29, ForkJoinPool-1-worker-31, ForkJoinPool-1-worker-13, ForkJoinPool-1-worker-21, ForkJoinPool-1-worker-7, ForkJoinPool-1-worker-9, ForkJoinPool-1-worker-27, ForkJoinPool-1-worker-23, ForkJoinPool-1-worker-11, ForkJoinPool-1-worker-17, ForkJoinPool-1-worker-3, ForkJoinPool-1-worker-25)

11.14 操作符


示例代码如下:

package com.atguigu.chapter11.test

object OperatorDemo01 {
  def main(args: Array[String]): Unit = {
    // 案例
    val n1 = 1
    val n2 = 2
    val r1 = n1 + n2  // 3
    val r2 = n1.+(n2) // 3 看 Int 的源码即可说明

    // 应用
    val monster = new Monster

    monster + 10
    monster.+(10)
    println("monster.money=" + monster.money) // 20

    println(monster ++)
    println(monster.++)
    println("monster.money=" + monster.money) // 22

    !monster
    println("monster.money=" + monster.money) // -22
  }
}

class Monster {
  var money: Int = 0

  // 对操作符进行重载(中置操作符)
  def +(n: Int): Unit = {
    this.money += n
  }

  // 对操作符进行重载(后置操作符)
  def ++(): Unit = {
    this.money += 1
  }

  // 对操作符进行重载(前置操作符:一元运算符)
  def unary_!(): Unit = {
    this.money = -this.money
  }
}

输出结果如下:

monster.money=20
()
()
monster.money=22
monster.money=-22

 

第十二章 模式匹配

12.1 match

1、基本介绍


2、Java Switch 的简单回顾


3、Scala 的 match 的快速入门案列
示例代码如下:

package com.atguigu.chapter12.mymatch

object MatchDemo01 {
  def main(args: Array[String]): Unit = {
    // 模式匹配,类似于 Java 的 switch 语法
    // 说明
    // 1. match (类似 java switch) 和 case 是关键字
    // 2. 如果匹配成功,则执行 => 后面的代码块(即代码可以有多行)
    // 3. 匹配的顺序是从上到下,匹配到一个就执行对应的代码
    // 4. => 后面的代码块,不要写 break,会自动的退出 match
    // 5. 如果一个都没有匹配到,则执行 case _ 后面的代码块
    val oper = '#'
    val n1 = 20
    val n2 = 10
    var res = 0
    oper match {
      case '+' => res = n1 + n2
      case '-' => res = n1 - n2
      case '*' => res = n1 * n2
      case '/' => res = n1 / n2
      case _ => println("oper error")
    }
    println("res=" + res)

  }
}

输出结果如下:

oper error
res=0

4、match 的细节和注意事项

12.2 守卫

1、基本介绍
  如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫。
2、Scala 的 守卫 的快速入门案例
示例代码如下:

package com.atguigu.chapter12.mymatch

object MatchIfDemo01 {
  def main(args: Array[String]): Unit = {

    for (ch <- "+-3!") { // 是对 "+-3!" 遍历
      var sign = 0
      var digit = 0
      ch match {
        case '+' => sign = 1
        case '-' => sign = -1
        // 特别注意:如果 case 后有 条件守卫即 if,那么这时的 _ 不是表示默认匹配,而是表示 忽略传入的ch
        case _ if ch.toString.equals("3") => digit = 3
        case _ if (ch > 1110 || ch < 120) => println("ch > 10")
        case _ => sign = 2
      }
      println(ch + " " + sign + " " + digit)
    }

  }
}

输出结果如下:

+ 1 0
- -1 0
3 0 3
ch > 10
! 0 0

3、练习题
示例代码如下:

package com.atguigu.chapter12.mymatch

object MatchExercise01 {
  def main(args: Array[String]): Unit = {

    for (ch <- "+-3!") {
      var sign = 0
      var digit = 0
      ch match {
        case '+' => sign = 1
        case '-' => sign = -1
        // 可以有多个 默认匹配,但是后面的默认匹配无效,编译器没有报错
        case _ => digit = 3
        case _ => sign = 2
      }
      // + 1 0    - -1 0    3 0 3   ! 0 3
      println(ch + " " + sign + " " + digit)
    }

  }
}

输出结果如下:

+ 1 0
- -1 0
3 0 3
! 0 3

示例代码如下:

package com.atguigu.chapter12.mymatch

object MatchExercise02 {
  def main(args: Array[String]): Unit = {

    for (ch <- "+-3!") {
      var sign = 0
      var digit = 0
      ch match {
        case _ => digit = 3
        case '+' => sign = 1
        case '-' => sign = -1
        // 说明..
      }
      // + 0 3  - 0 3   3 0 3     ! 0 3
      println(ch + " " + sign + " " + digit)
    }

  }
}

输出结果如下:

+ 0 3
- 0 3
3 0 3
! 0 3

12.3 模式中的变量

1、基本介绍
  如果在 case 关键字后跟变量名,那么 match 前表达式的值会赋给那个变量。
2、Scala 的 模式中的变量 的快速入门案例
示例代码如下:

package com.atguigu.chapter12.mymatch

object MatchVarDemo01 {
  def main(args: Array[String]): Unit = {
    val ch = 'U'
    ch match {
      case '+' => println("ok~")
      // 下面 case mychar 含义是 mychar = ch
      case mychar => println("ok~~" + mychar)
      case _ => println("ok~~~")
    }

    val ch1 = '+'
    // 在 Scala 中 match 是一个表达式,因此可以有返回值
    // 返回值就是匹配到的代码块的最后一句话的值
    val res = ch1 match {
      case '+' => ch1 + "hello"
      case _ => println("ok~~")
    }
    println("res=" + res)

  }
}

输出结果如下:

ok~~U
res=+hello

12.4 类型匹配

1、基本介绍
  可以匹配对象的任意类型,这样做避免了使用 isInstanceOf 和 asInstanceOf 方法。
2、Scala 的 类型匹配 的快速入门案例
示例代码如下:

package com.atguigu.chapter12.mymatch

object MatchTypeDemo01 {
  def main(args: Array[String]): Unit = {
    val a = 8
    // 根据 a 的值来返回 obj 实例的类型
    val obj = if (a == 1) 1
    else if (a == 2) "2"
    else if (a == 3) BigInt(3)
    else if (a == 4) Map("aa" -> 1)
    else if (a == 5) Map(1 -> "aa")
    else if (a == 6) Array(1, 2, 3)
    else if (a == 7) Array("aa", 1)
    else if (a == 8) Array("aa")

    // 根据 obj 的类型来匹配并返回
    val result = obj match {
      case a: Int => a
      case _ : BigInt => Int.MaxValue // 如果 case _ 出现在 match 中间,则表示隐藏变量名,即不使用,而不是表示默认匹配。
      case b: Map[String, Int] => "对象是一个[字符串-数字]的Map集合"
      case c: Map[Int, String] => "对象是一个[数字-字符串]的Map集合"
      case d: Array[String] => d // "对象是一个字符串数组"  // 如果在 case 关键字后跟变量名,那么 match 前表达式的值会赋给那个变量。
      case e: Array[Int] => "对象是一个数字数组"
      case f: BigInt => Int.MaxValue
      case y: Float => println("xxx")
      case _ => "啥也不是"
    }
    println(result)

  }
}

输出结果如下:

[Ljava.lang.String;@23223dd8

3、类型匹配注意事项

12.5 数组匹配

1、基本介绍


2、Scala 的 匹配数组 的快速入门案例
示例代码如下:

package com.atguigu.chapter12.mymatch

object MatchArrayDemo01 {
  def main(args: Array[String]): Unit = {

    val arrs = Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0), Array(1, 1, 0, 1))
    for (arr <- arrs) {
      val result = arr match {
        case Array(0) => "0"
        case Array(x, y) => x + "=" + y
        case Array(0, _*) => "以0开头和数组"
        case _ => "什么集合都不是"
      }
      println("result = " + result)
    }

    println("--------------------")

    // 给你一个数组集合,如果该数组是 Array(10, 20),请使用默认匹配,返回 Array(20, 10)
    val arrs2 = Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0), Array(1, 1, 0, 1))

    for (arr <- arrs2 ) {
      val result = arr match {
        case Array(x, y) => ArrayBuffer(y, x) // Array(y, x).toBuffer
        case _ => "不处理"
      }
      println("result = " + result)
    }
  }
}

输出结果如下:

result = 0
result = 1=0
result = 以0开头和数组
result = 什么集合都不是
result = 什么集合都不是
--------------------
result = 不处理
result = ArrayBuffer(0, 1)
result = 不处理
result = 不处理
result = 不处理

12.6 列表匹配

示例代码如下:

package com.atguigu.chapter12.mymatch

object MatchListDemo01 {
  def main(args: Array[String]): Unit = {

    for (list <- Array(List(88), List(0), List(1, 0), List(0, 0, 0), List(1, 0, 0))) {
      val result = list match {
        case x :: Nil => x // 如果要匹配 List(88) 这样的只含有一个元素的列表,并原值返回
        case 0 :: Nil => "0" // List 只含有0元素
        case x :: y :: Nil => x + " " + y // List 只含有两个元素
        case 0 :: tail => "0 ..." // List 是以0开头的,后面任意
        case _ => "something else"
      }
      println(result)

    }
  }
}

输出结果如下:

88
0
1 0
0 ...
something else

12.7 元组匹配

示例代码如下:

package com.atguigu.chapter12.mymatch

object MatchTupleDemo01 {
  def main(args: Array[String]): Unit = {
    // 如果要匹配 (10, 30) 这样任意两个元素的对偶元组,应该如何写
    for (pair <- Array((0, 1), (1, 0), (10, 30), (1, 1), (1, 0, 2))) {
      val result = pair match {
        case (0, _) => "0 ..." // 要匹配以0打头的二元组
        case (y, 0) => y // 要匹配第一个元素任意,第二个元素为0的二元组
        case (x, y) => (y, x)
        case _ => "other"
      }
      println(result)
    }

  }
}

输出结果如下:

0 ...
1
(30,10)
(1,1)
other

12.8 对象匹配

1、基本介绍


2、Scala 的 对象匹配 的快速入门案例
应用案例1示例代码如下:

package com.atguigu.chapter12.mymatch

object MatchObjectDemo01 {
  def main(args: Array[String]): Unit = {

    // 模式匹配使用
    // val number: Double = 36.0
    val number: Double = Square(6.0)

    number match {
      // 说明 case Square(n) 的运行的机制
      // 1. 当匹配到 case Square(n)
      // 2. 调用 Square 的 unapply(z: Double) z的值就是 number
      // 3. 如果对象提取器 unapply(z: Double) 返回的是 Some(6),则表示匹配成功,同时将6赋给 Square(n) 的n
      // 4. 如果对象提取器 unapply(z: Double) 返回的是 None,则表示匹配不成功
      case Square(n) => println("匹配成功,n=" + n)
      case _ => println("nothing matched")
    }

  }
}

object Square {
  // 说明
  // 1. unapply 方法是对象提取器
  // 2. 接收 z: Double 类型
  // 3. 返回类型是 Option[Double]
  // 4. 返回的值是 Some(math.sqrt(z)) 返回z的开平方的值,并放入到 Some(x)
  def unapply(z: Double): Option[Double] = {
    println("unapply被调用,z=" + z)
    Some(math.sqrt(z))
    // None...
  }
  def apply(z: Double): Double = z * z
}

输出结果如下:

unapply被调用,z=36.0
匹配成功,n=6.0

应用案例1的小结:


应用案例2示例代码如下:

package com.atguigu.chapter12.mymatch

object MatchObjectDemo02 {
  def main(args: Array[String]): Unit = {
    val namesString = "Alice,Bob,Thomas" // 字符串对象的构造
    namesString match {
      // 当 执行 case Names(first, second, third)
      // 1. 会调用 unapplySeq(str) 把 "Alice,Bob,Thomas" 传入给 str
      // 2. 如果 返回的是 Some("Alice","Bob","Thomas") 分别给 (first, second, third)
      //    注意:这里的返回的值的个数需要和 (first, second, third) 要一样
      // 3. 如果返回的 None,表示匹配失败
      case Names(first, second, third) => {
        println("the string contains three people's names")
        // 打印字符串
        println(s"$first $second $third")
      }
      case _ => println("nothing matched")
    }

  }
}

object Names {
  // 当构造器是多个参数时,就会触发这个对象提取器
  def unapplySeq(str: String): Option[Seq[String]] = {
    if (str.contains(",")) Some(str.split(",")) else None
  }
}

输出结果如下:

the string contains three people's names
Alice Bob Thomas

应用案例2的小结:

12.9 变量声明中的模式

1、基本介绍
  match 中每一个 case 都可以单独提取出来,意思是一样的。
2、Scala 中 变量声明中的模式 快速入门案例
示例代码如下:

package com.atguigu.chapter12.mymatch

object MatchVarDemo02 {
  def main(args: Array[String]): Unit = {
    val (x, y, z) = (1, 2, "hello") // 一次性可以定义多个变量
    println("x=" + x)

    val (q, r) = BigInt(10) /% 3  // q = BigInt(10) / 3  r = BigInt(10) % 3

    val arr = Array(1, 7, 2, 9)
    val Array(first, second, _*) = arr // 提出arr的前两个元素
    println(first, second)
  }
}

输出结果如下:

x=1
(1,7)

12.10 for表达式中的模式

1、基本介绍
  for循环也可以进行模式匹配。
2、Scala 中 for表达式中的模式 快速入门案例
示例代码如下:

package com.atguigu.chapter12.mymatch

object MatchForDemo01 {
  def main(args: Array[String]): Unit = {
    val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
    for ((k, v) <- map) {
      println(k + " -> " + v)
    }

    println("----------")
    // 只遍历出 value =0 的 key-value,其它的过滤掉
    for ((k, 0) <- map) { // 过滤,简洁高效
      println(k + " --> " + 0)
    }

    println("----------")
    // 这个就是上面代码的另外写法,只是下面的用法灵活和强大
    for ((k, v) <- map if v == 0) { // for 循环中的守卫
      println(k + " ---> " + v)
    }

  }
}

输出结果如下:

A -> 1
B -> 0
C -> 3
----------
B --> 0
----------
B ---> 0

12.11 样例(模板)类

1、Scala 样例(模板)类 的快速入门案例
示例代码如下:

package com.atguigu.chapter12.caseclass

object CaseClassDemo01 {
  def main(args: Array[String]): Unit = {
    println("ok")
  }
}

// 说明: 这里的 Dollar, Currencry, NoAmount 是样例类。
abstract class Amount
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
case object NoAmount extends Amount

2、基本介绍

3、样例类最佳实践1
  当我们有一个类型为 Amount 的对象时,可以用模式匹配来匹配他的类型,并将属性值绑定到变量(即:把样例类对象的属性值提取到某个变量,该功能非常有用!)
示例代码如下:

package com.atguigu.chapter12.caseclass

/**
  * 1、当我们有一个类型为 Amount 的对象时,可以用模式匹配来匹配他的类型,并将属性值绑定到变量(即:把样例类对象的属性值提取到某个变量,该功能非常有用!)
  */
object CaseClassDemo02 {
  def main(args: Array[String]): Unit = {
    // 该案例的作用:体验使用样例类方式进行对象匹配简洁性
    for (amt <- Array(Dollar2(1000.0), Currency2(1000.0, "RMB"), NoAmount2)) {
      val result = amt match {
        // 说明:即我们在使用样例类进行对象匹配的时候,不需要手写 unapply 方法了
        case Dollar2(v) => "$" + v
        // 说明
        case Currency2(v, u) => v + " " + u
        case NoAmount2 => ""
      }
      println(amt + ": " + result)
    }

  }
}

// 说明: 这里的 Dollar2, Currencry2, NoAmount2 是样例类。
abstract class Amount2

case class Dollar2(value: Double) extends Amount2

case class Currency2(value: Double, unit: String) extends Amount2

case object NoAmount2 extends Amount2

输出结果如下:

Dollar2(1000.0): $1000.0
Currency2(1000.0,RMB): 1000.0 RMB
NoAmount2: 

4、样例类最佳实践2
  样例类的 copy 方法和带名参数,copy 可以创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性。
示例代码如下:

package com.atguigu.chapter12.caseclass

/**
  * 2、样例类的 copy 方法和带名参数,copy 可以创建一个与现有对象值相同的新对象,并可以通过带名参数来修改某些属性
  */
object CaseClassDemo03 {
  def main(args: Array[String]): Unit = {
    val amt = Currency3(29.95, "RMB")
    val amt1 = amt.copy() // 创建了一个新的对象,属性值和上面一样
    val amt2 = amt.copy(value = 19.95)  // 创建了一个新对象,但是修改了value属性
    val amt3 = amt.copy(unit = "英镑")  // 创建了一个新对象,但是修改了unit属性
    println(amt)
    println(amt1)
    println(amt2)
    println(amt3)
  }
}

// 说明: 这里的 Dollar3, Currencry3, NoAmount3 是样例类。
abstract class Amount3

case class Dollar3(value: Double) extends Amount3

case class Currency3(value: Double, unit: String) extends Amount3

case object NoAmount3 extends Amount3

输出结果如下:

Currency3(29.95,RMB)
Currency3(29.95,RMB)
Currency3(19.95,RMB)
Currency3(29.95,英镑)

12.12 case 语句的中置(缀)表达式

1、基本介绍
  什么是中置表达式?
  答:1 + 2,这就是一个中置表达式。如果 unapply 方法产出一个元组,你可以在 case 语句中使用中置表示法。比如:可以匹配一个 List 序列。
2、快速入门案例
示例代码如下:

package com.atguigu.chapter12.mymatch

object MidCaseDemo01 {
  def main(args: Array[String]): Unit = {

    List(1, 3, 5, 9) match { // 修改并测试
      // 1.两个元素间::叫中置表达式,至少first,second两个匹配才行。
      // 2.first 匹配第一个,second 匹配第二个,rest 匹配剩余部分 (5, 9)
      case first :: second :: rest => println(first + " " + second + " " + rest.length + " " + rest) // 1 3 2 List(5, 9)
      case _ => println("匹配不到...")
    }

  }
}

输出结果如下:

1 3 2 List(5, 9)

12.13 匹配嵌套结构

1、基本介绍
  匹配嵌套的操作原理类似于正则表达式。
2、匹配嵌套结构 的最佳实践案例-商品捆绑打折出售
现在有一些商品,请使用 Scala 设计相关的样例类,完成商品捆绑打折出售。要求:
  1、商品捆绑可以是单个商品,也可以是多个商品。
  2、打折时按照折扣x元进行设计。
  3、能够统计出所有捆绑商品打折后的最终价格。
创建样例类

// 先设计样例类
abstract class Item // 项

// 商品,以书籍为例
case class Book(description: String, price: Double) extends Item

// 商品,以食品为例
// 商品,以酒水为例...

// Bundle 捆绑类
case class Bundle(description: String, discount: Double, item: Item*) extends Item

匹配嵌套结构(就是 Bundle 的对象)

    // 具体的打折案例表示:有一捆书,漫画(一本)(40-10) + 文学作品(两本)(80+30-20) = 30 + 90 = 120.0
    val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30)))

为了讲解案列,补充三个新的知识点:
知识点1-将 desc 绑定到第一个 Book 的描述。


知识点2-通过 @ 表示法将嵌套的值绑定到变量。*_绑定剩余元素到 rest。

知识点3-不使用 * 绑定剩余 Item 到 rest。



3个知识点的所有示例代码如下:

package com.atguigu.chapter12.caseclass

object SalesDem01 {
  def main(args: Array[String]): Unit = {
    // 具体的打折案例表示:有一捆书,漫画(一本)(40-10) + 文学作品(两本)(80+30-20) = 30 + 90 = 120.0
    val sale = Bundle("书籍", 10, Book("漫画", 40), Bundle("文学作品", 20, Book("《阳关》", 80), Book("《围城》", 30)))

    // 知识点1:取出这个嵌套结构中的 "漫画"的描述
    val res = sale match  {
      // 如果我们进行对象匹配时,不想接受某些值,则使用_ 忽略即可,_* 表示忽略所有。
      case Bundle(_, _, Book(desc, _), _*) => desc
    }
    println("res=" + res)

    // 知识点2:如何将 "漫画" 和 这个嵌套结构中的 "漫画" 和 紫色的部分 绑定到变量,即赋值到变量中。
    val res2 = sale match {
      case Bundle(_, _, art @ Book(_, _), rest @ _*) => (art, rest)
    }
    println("art=" + res2._1)
    println("rest=" + res2._2)

    // 知识点3:如何将 "漫画" 和 这个嵌套结构中的 "漫画" 和 紫色的部分 绑定到变量,即赋值到变量中。明确说明没有多个 Bundle。
    val res3 = sale match {
      case Bundle(_, _, art @ Book(_, _), rest) => (art, rest) // rest 等价于 rest @ _
    }
    println("art=" + res3._1)
    println("rest=" + res3._2)

    // 匹配嵌套案例完成
    def price(it: Item): Double = {
      it match {
        case Book(_, price) => price
        // 生成一个新的集合,_是将 its 中每个循环的元素传递到 price 中 it 中。递归操作
        case Bundle(_, disc, its @ _*) => its.map(price).sum - disc
      }
    }
    println(price(sale))
  }
}

// 先设计样例类
abstract class Item // 项

// 商品,以书籍为例
case class Book(description: String, price: Double) extends Item

// 商品,以食品为例
// 商品,以酒水为例...

// Bundle 捆绑类
case class Bundle(description: String, discount: Double, item: Item*) extends Item

输出结果如下:

res=漫画
art=Book(漫画,40.0)
rest=WrappedArray(Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0))))
art=Book(漫画,40.0)
rest=Bundle(文学作品,20.0,WrappedArray(Book(《阳关》,80.0), Book(《围城》,30.0)))
120.0

12.14 密封类

1、基本介绍 sealed


2、密封类卫 的快速入门案例

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SoWhat1412

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

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

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

打赏作者

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

抵扣说明:

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

余额充值