第九部分 集合

第 1 节 可变和不可变集合

  • 根据容器中元素的组织方式和操作方式,可以分为有序和无序、可变和不可变等不同的容器类别;
  • 不可变集合 是指集合内的元素一旦初始化完成就不可再进行更改,任何对集合的改变都将生成一个新的集合;
  • 可变集合 提供了改变集合内元素的方法;
  • Scala同时支持可变集合和不可变集合,主要下面两个包:
    • scala.collection.mutable:定义了可变集合的特质和具体实现类
    • scala.collection.immutable:定义了不可变集合的特质和具体实现类
  • 对于几乎所有的集合类,Scala都同时提供了可变和不可变的版本。
  • Scala优先采用不可变集合 ,不可变集合元素不可更改,可以安全的并发访问。
  • Scala集合有三大类:Seq(序列)、Set(集)、Map(映射);
  • 所有的集合都扩展自Iterable特质。

image.png

image.png

image.png

  • 小结:

    • String属于IndexedSeq
    • Queue队列和Stack堆这两个经典的数据结构属于LinearSeq
    • Map体系下有一个SortedMap,说明Scala中的Map是可以支持排序的
    • mutable可变集合中Seq中的Buffer下有ListBuffer,它相当于可变的List列表;
    • List列表属于Seq中的LinearSeq

      第 2 节 Seq

  • Seq代表按照一定顺序排列的元素序列;

  • 该序列是一种特别的可迭代集合,包含可重复的元素;
  • 元素的顺序是确定的,每个元素对应一个索引值;
  • Seq提供了两个重要的子特质:

    • IndexedSeq:提供了快速随机访问元素的功能,它通过索引来查找和定位的
    • LinearSeq:提供了访问head、tail的功能,它是线型的,有头部和尾部的概念,通过遍历来查找。

      2.1 List

  • List代表元素顺序固定的不可变的链表,它是Seq的子类,在Scala编程中经常使用。

  • List是函数式编程语言中典型的数据结构,与数组类似,可索引、存放类型相同的元素。
  • List一旦被定义,其值就不能改变。
  • List列表有头部和尾部的概念,可以分别使用head和tail方法来获取:
    • head返回的是列表第一个元素的值
    • tail返回的是除第一个元素外的其它元素构成的新列表
  • 这体现出列表具有递归的链表结构。

  • Scala定义了一个空列表对象Nil,定义为List[Nothing]

  • 借助 Nil 可将多个元素用操作符 :: 添加到列表头部,常用来初始化列表;
  • 操作符 ::: 用于拼接两个列表;

    1. // 构建List
    2. val lst1 = 1 :: 2:: 3:: 4 :: Nil
    3. // :: 是右结合的
    4. val lst2 = 1 :: (2:: (3:: (4 :: Nil)))
    5. // 使用 ::: 拼接List
    6. val lst3 = lst1 ::: lst2
    7. // 使用 head、tail获取头尾
    8. lst3.head //返回第一个元素
    9. lst3.tail //返回除第一个元素外的其它元素构成的新列表
    10. lst3.init //返回除最后一个元素外的其它元素构成的新列表
    11. lst3.last //返回最后一个元素
  • 列表递归的结构,便于编写递归的算法: ```scala package cn.lagou.edu.scala.section3 import scala.util.Random

object test1 { def main(args: Array[String]): Unit = { val random = new Random(100) val lst = List.fill(100)(random.nextInt(200)) println(lst) println(s”sum(lst) = ${sum(lst)}; sum(lst) = ${lst.sum}”) println(quickSort(lst)) } // 快排 def quickSort(lst: List[Int]): List[Int] = { lst match { case Nil => Nil case head :: tail => val (less, greater) = tail.partition(_ < head) quickSort(less) ::: head :: quickSort(greater) } } }

<a name="00b36f60"></a>
### 2.2 Queue

- 队列Queue是一个先进先出的结构。
- 队列是一个有序列表,在底层可以用数组或链表来实现。
- 先进先出的原则,就是先存入的数据,要先取出,后存入的数据后取出。
- 在Scala中,有scala.collection.mutable.Queue和scala.collection.immutable.Queue,一般来说,我们使用的是scala.collection.mutable.Queue
```scala
//创建可变的队列 
val queue1 = new collection.mutable.Queue[Int]() 
println(queue1) 

//队列当中添加元素 
queue1 += 1 

//队列当中添加List 
queue1 ++= List(2,3,4) 
println(queue1) 

// 按照进入队列顺序,删除队列当中的元素(弹出队列) 
// 返回队列中的第一个元素,并从队列中删除该元素。 
val dequeue = queue1.dequeue() 
println(dequeue) 
println(queue1) 

// 向队列当中加入元素(入队列操作)
// 元素入队列 
queue1.enqueue(5,6,7) 
println(queue1) 

//获取第一个、最后一个元素
println(queue1.head) 
println(queue1.last)

第 3 节 Set

  • Set(集合)是没有重复元素的对象集合,Set中的元素是唯一的;
  • Set分为可变的和不可变的集合;
  • 默认情况下,使用的是不可变集合(引用 scala.collection.immutable.Set);
  • 使用可变集合,需要引用 scala.collection.mutable.Set 包;

    object SetDemo { 
    def main(args: Array[String]): Unit = {
      // 判断元素是否存在 
      val set = Set(1, 2, 3, 4, 5, 6, 7) 
      println(set.exists(_ % 2 == 0)) 
    
      // 删除元素 
      set.drop(1)
    
      // 引入可变的Set 
      import scala.collection.mutable.Set 
      val mutableSet = Set(4, 5, 6) 
    
      // 增加元素、删除元素;执行成功返回true,否则返回false 
      mutableSet.add(7)
      println(mutableSet) 
      mutableSet.remove(7) 
      println(mutableSet) 
    
      // 使用 += / -= 增加、删除元素,表达更简洁 
      mutableSet += 5 
      mutableSet -= 2 
    
      // 集合典型操作交、并、差 
      //交集(&、intersect) 
      println(Set(1, 2, 3) & Set(2, 3, 4)) 
      println(Set(1, 2, 3).intersect(Set(2, 3, 4))) 
      println(Set(1, 2, 3) intersect (Set(2, 3, 4))) 
    
      //并集(++、|、union) 
      println(Set(1, 2, 3) ++ Set(2, 3, 4)) 
      println(Set(1, 2, 3) | Set(2, 3, 4)) 
      println(Set(1, 2, 3).union(Set(2, 3, 4))) 
    
      //差集(--、&~、diff) 
      //返回:包含本集合中不包含在给定集合中的元素的集合 
      println(Set(1, 2, 3) -- Set(2, 3, 4)) 
      println(Set(1, 2, 3) &~ Set(2, 3, 4))
      println(Set(1, 2, 3).diff(Set(2, 3, 4)))
    } 
    }
    

    第 4 节 Map

  • Map(映射)是一系列键值对的容器;Scala 提供了可变的和不可变的两种版本的Map,分别定义在包 scala.collection.mutable 和 scala.collection.immutable 里;

  • 默认情况下,Scala中使用不可变的 Map;
  • 如果要使用可变Map,必须导入scala.collection.mutable.Map;
  • 在Map中,键的值是唯一的,可以根据键来对值进行快速的检索。 ```scala // 可使用两种方式定义Map // Map缺省是不可变的,值不能更改 val a = Map(“a” -> 1, “b” -> 2, “c” -> 3) val a = Map((“a”, 1),(“b”, 2),(“c”, 3)) a.keys a.values

// 获取Map中的值: a(“a”)

// 访问不存在的key时,会抛出异常。Java.util.NoSuchElementException: key not found: x a(“x”)

// 使用get方法,返回一个Option对象,要么是Some(键对应的值),要么是None a.get(“a”)

// 获取键对应的值,如果键不存在返回给定的值(这里是0) a.getOrElse(“a”, 0)

// 更新Map中的值(要使用可变的Map) val b = scala.collection.mutable.Map(“a” -> 1, “b” -> 2, “c” -> 3) b(“a”) = 2

// 增加了一个新元素 b(“d”) = 4

// 用 += 添加新的元素;用 –= 删除元素 b += (“e” -> 1, “f” -> 2) b -= “a”

// 增加元素 val b = Map(“a” -> 1, “b” -> 2, “c” -> 3) val c = b + (“a” -> 10, “b” -> 20)

// 通过包含键值对的二元组创建Map集合 val a = Map((“a”, 1),(“b”, 2),(“c”, 3))

// 逐个访问 value for(v <- a.values) println(v)

// key 和 value 做了交换 val b = for((k,v) <- a) yield (v,k)

// 下面才是具有scala风格的写法,推荐 a.map(x=>(x._2, x._1))

// 拉链操作创建Map val a = Array(1,2,3) val b = Array(“a”,”b”,”c”) //c: Array[(Int, String)] val c = a.zip(b) //c: scala.collection.immutable.Map[Int,String] val c = a.zip(b).toMap

<a name="9390e805"></a>
## 第 5 节 集合常用算子
<a name="4d5e6e44"></a>
### 5.1 map、foreach & mapValues

- 集合对象都有 foreach、map 算子。
- 两个算子的共同点在于:都是用于遍历集合对象,并对每一项执行指定的方法;
- 两个算子的差异点在于:
   - foreach无返回值(准确说返回void),用于遍历集合
   - map返回集合对象,用于将一个集合转换成另一个集合
```scala
// 使用 foreach 打印集合元素 
val numlist = (1 to 10).toList 
numlist.foreach(elem=>print(elem+" ")) 
numlist.foreach(print _) 
numlist.foreach(print) 

// 使用 map 对集合进行转换 
numlist.map(_ > 2) 
numlist.map(_ * 2)
  • 操作 Map集合时,mapValues用于遍历value,是map操作的一种简化形式; ```scala // Range(20, 0, -2)用给定的步长值设定一个范围,从开始到结束(不包含)。 //Map(20 -> 0,18 -> 1,16 -> 2,14 -> 3,12 -> 4,10 -> 5,8 -> 6,6 -> 7,4 -> 8,2 -> 9) val map = Range(20, 0, -2).zipWithIndex.toMap

// 将map集合中的value值+100 map.map(elem => (elem._1, elem._2 + 100)) map.map{case (k,v) => (k, v+100)}

// mapValues的表达最简洁 map.mapValues(_+100)

<a name="1b635bbb"></a>
### 5.2 flatten & flatMap

- flatten的作用是把嵌套的结构展开,把结果放到一个集合中;
- 在 flatMap 中传入一个函数,该函数对每个输入都返回一个集合(而不是一个元素),最后把生成的多个集合“拍扁”成为一个集合;
```scala
scala> val lst1 = List(List(1,2), List(3,4)) 
lst1: List[List[Int]] = List(List(1, 2), List(3, 4)) 

scala> lst1.flatten 
res5: List[Int] = List(1, 2, 3, 4) 

// flatten 把一个字符串的集合展开为一个字符集合,因为字符串本身就是字符的集合 
scala> val lst4 = List("Java", "hadoop") 
lst4: List[String] = List(Java, hadoop) 

scala> lst4.flatten 
res8: List[Char] = List(J, a, v, a, h, a, d, o, o, p) 

// flatten 有效的处理 Some 和 None 组成的集合。它可以展开Some元素形成一个新的集合,同时去掉 None元素
scala> val x = Array(Some(1), None, Some(3), None)
x: Array[Option[Int]] = Array(Some(1), None, Some(3), None)

// 方法很多,flatten最简单 
scala> x.flatten 
res9: Array[Int] = Array(1, 3) 

scala> x.collect{case Some(i) => i}
res10: Array[Int] = Array(1, 3)

scala> x.filter(!_.isEmpty).map(_.get)
res11: Array[Int] = Array(1, 3)
// 下面两条语句等价 
val lst = List(List(1,2,5,6),List(3,4)) 

// 将 lst 中每个元素乘2,最后作为一个集合返回 
// 此时 flatMap = flatten + map 
//List(1,2,5,6,3,4) 
lst.flatten.map(_*2) 
lst.flatMap((x: List[Int]) => x.map(_*2)) 
lst.flatMap(_.map(_*2)) 

// 将字符串数组按空格切分,转换为单词数组 
val lines = Array("Apache Spark has an advanced DAG execution engine", "Spark offers over 80 high-level operators") 
// 下面两条语句效果等价 
//map算子产生的结果:Array(Array(Apache, Spark, has, an, advanced, DAG, execution, engine), Array(Spark, offers, over, 80, high-level, operators)) 
// flatten算子产生的结果:Array(Apache, Spark, has, an, advanced, DAG, execution, engine, Spark, offers, over, 80, high-level, operators) 
lines.map(_.split(" ")).flatten 
// 此时 flatMap = map + flatten 
lines.flatMap(_.split(" "))
  • 备注:flatMap = flatten + map 或 flatMap = map + flatten

    5.3 collect

  • collect通过执行一个并行计算(偏函数),得到一个新的数组对象

    object CollectDemo { 
    //通过下面的偏函数,把chars数组的小写a转换为大写的A 
    val fun: PartialFunction[Char, Char] = { 
      case 'a' => 'A' 
      case x => x 
    }
    def main(args: Array[String]): Unit = { 
      val chars = Array('a', 'b', 'c') 
      val newchars = chars.collect(fun) 
      println("newchars:" + newchars.mkString(",")) 
    } 
    }
    

    5.4 reduce

  • reduce可以对集合当中的元素进行归约操作;

  • 还有 reduceLeft 和 reduceRight ,reduceLeft 从左向右归约,reduceRight 从右向左归约; ```scala val lst1 = (1 to 10).toList lst1.reduce(+)

// 为什么这里能出现两个占位符? lst1.reduce(+) // 我们说过一个占位符代表一个参数,那么两个占位符就代表两个参数。根据这个思路改写等价的语句 // x类似于buffer,缓存每次操作的数据;y每次操作传递新的集合元素 lst1.reduce((x, y) => x + y) // 利用reduce操作,查找 lst1 中的最大值 lst1.reduce((x,y) => if (x>y) x else y)

// reduceLeft、reduceRight lst1.reduceLeft((x,y) => if (x>y) x else y) lst1.reduceRight((x,y) => if (x>y) x else y)

<a name="8b5e364e"></a>
### 5.5 sorted sortwith & sortby

- **Scala中对于集合的排序有三种方法:sorted、sortBy、sortWith**
```scala
object SortDemo { 
  def main(args: Array[String]): Unit = { 
    val list = List(1, 9, 3, 8, 5, 6) 
    //sorted方法对一个集合进行自然排序 
    //sorted源码:def sorted[B >: A](implicit ord: Ordering[B]): Repr 
    //源码中有两点值得注意的地方: 
    // 1.sorted方法中有个隐式参数ord: Ordering。 
    // 2.sorted方法真正排序的逻辑是调用的java.util.Arrays.sort 
    val numSort: List[Int] = list.sorted 
    println(numSort) 

    //sortBy源码:def sortBy[B](f: A => B)(implicit ord: Ordering[B]): Repr = sorted(ord on f) 
    //sortBy最后调用的sorted方法 
    println(list.sortBy(x => x).reverse) 

    //sortWith源码:def sortWith(lt: (A, A) => Boolean): Repr = sorted(Ordering fromLessThan lt) 
    print(list.sortWith(_ > _)) 
  } 
}

第 6 节 与Java集合的转换

  • 使用 scala.collection.JavaConverters 与Java集合交互。它有一系列的隐式转换,添加了asJava和asScala的转换方法。 ```scala import scala.collection.JavaConverters._

val list: Java.util.List[Int] = List(1,2,3,4).asJava val buffer: scala.collection.mutable.Buffer[Int] = list.asScala

<a name="3176dc00"></a>
# 第十部分 隐式机制
<a name="bdb92975"></a>
## 第 1 节 隐式转换

- 隐式转换和隐式参数是Scala中两个非常强大的功能,利用隐式转换和隐式参数,可以提供类库,对类库的使用者隐匿掉具体的细节。
- Scala会根据隐式转换函数的签名,在程序中使用到隐式转换函数接收的参数类型定义的对象时,会自动将其传入隐式转换函数,转换为另外一种类型的对象并返回,这就是“ **隐式转换** ”。
   - 首先得有一个隐式转换函数
   - 使用到隐式转换函数接收的参数类型定义的对象
   - Scala自动传入隐式转换函数,并完成对象的类型转换
- 隐式转换需要使用implicit关键字。
- 使用Scala的隐式转换有一定的限制:
   - implicit关键字只能用来修饰方法、变量、参数
   - 隐式转换的函数只在当前范围内才有效。如果隐式转换不在当前范围内定义,那么必须通过import语句将其导入
- Spark源码中有大量的隐式转换和隐式参数,因此必须掌握隐式机制。
<a name="2142fbd6"></a>
## 第 2 节 隐式转换函数

- Scala的隐式转换最核心的就是定义隐式转换函数,即implicit conversion function。
- 定义的隐式转换函数,只要在编写的程序内引入,就会被Scala自动使用。
- 隐式转换函数由Scala自动调用,通常建议将隐式转换函数的名称命名为“one2one”的形式。

- **示例 1 :下面代码中定义了一个隐式函数**
```scala
class Num {} 

class RichNum(num: Num) { 
  def rich(): Unit = { 
    println("Hello Implicit!") 
  } 
}

object ImplicitDemo { 
  // 定义一个名称为num2RichNum的隐式函数 
  implicit def num2RichNum(num: Num): RichNum = { 
    new RichNum(num) 
  }
  def main(args: Array[String]): Unit = { 
    val num = new Num 
    // num对象并没有rich方法,编译器会查找当前范围内是否有可转换的函数 
    // 如果没有则编译失败,如果有则会调用。 
    num.rich() 
  } 
}
  • 示例 2 : 导入隐式函数 ```scala package test.implicitdemo

object Int2String { implicit def int2String(num: Int):String = num.toString }


- 下面代码中调用了String类型的length方法,Int类型本身没有length方法,但是在可用范围内定义了可以把Int转换为String的隐式函数int2String,因此函数编译通过并运行出正确的结果。
- 此示例中隐式函数的定义必须定义在使用之前,否则编译报错。
```scala
import test.implicitdemo.Int2String._ 

object ImplicitTest { 
  def main(args: Array[String]): Unit = { 
    println(20.length) 
  } 
}
  • 通过import test.implicitdemo.Int2String._,将Int2StringTest内部的成员导入到相应的作用域内,否则无法调用隐式函数。
  • 要实现隐式转换,只要在程序可见的范围内定义隐式转换函数即可,Scala会自动使用隐式转换函数。
  • 隐式转换函数与普通函数的语法区别就是,要以implicit开头,而且最好要定义函数返回类型。

  • 隐式转换案例:特殊售票窗口(只接受特殊人群买票,比如学生、老人等),其他人不能在特殊售票窗口买票。 ```scala class SpecialPerson(var name: String)

class Older(var name: String)

class Student(var name: String)

class Worker(var name: String)

object ImplicitDemoTwo { def buySpecialTickWindow(person: SpecialPerson): Unit = { if (peron != null) { println(person.name + “购买了一张特殊票!”) } else { println(“你不是特殊人群,不能在此买票!”) } } //隐式转换函数 //注意:any参数的类型是Any implicit def any2SpecialPerson(any: Any): SpecialPerson = { any match { case any: Older => new SpecialPerson(any.asInstanceOf[Older].name) case any: Student => new SpecialPerson(any.asInstanceOf[Student].name) case _ => null } }

def main(args: Array[String]): Unit = { val stu = new Student(“jacky”) val older = new Older(“old man”) val worker = new Worker(“tom”) ImplicitDemoTwo.buySpecialTickWindow(stu) ImplicitDemoTwo.buySpecialTickWindow(older) ImplicitDemoTwo.buySpecialTickWindow(worker) } }

<a name="80b9e625"></a>
## 第 3 节 隐式参数和隐式值

- 在函数定义的时候,支持在 **最后一组参数** 中使用 implicit,表明这是一组隐式参数。在调用该函数的时候,可以不用传递隐式参数,而编译器会自动寻找一个implicit标记过的合适的值作为参数。
- Scala编译器会在两个范围内查找:
   - 当前作用域内可见的val或var定义隐式变量
   - 隐式参数类型的伴生对象内隐式值
```scala
object Doubly {
  //在print函数中定义一个隐式参数fmt 
  def print(num: Double)(implicit fmt: String): Unit = { 
    println(fmt format (num)) 
  }

  def main(args: Array[String]): Unit = { 
    //此时调用print函数需要为第二个隐式参数赋值 
    print(3.12)("%.1f") 

    //定义一个隐式变量 
    implicit val printFmt="%.3f" 
    //当调用print函数时没有给第二个隐式参数赋值, 
    //那么Scala会在当前作用域内寻找可见的val或var定义的隐式变量,一旦找到就会应用 
    print(3.12) 
  }
}

第十一部分 扩展部分

第 1 节 类型参数

  • Scala的类型参数与Java的泛型是一样的,可以在集合、类、函数中定义类型参数,从而保证程序更好的健壮性。

    1.1 泛型类

  • 泛型类,顾名思义,其实就是在类的声明中定义一些泛型类型,然后在类内部的字段或者方法,就可以使用这些泛型类型。

  • 使用泛型类,通常是需要对类中的某些成员,比如某些字段和方法中的参数或变量进行统一的类型限制,这样可以保证程序更好的健壮性和稳定性。
  • 如果不使用泛型进行统一的类型限制,那么在后期程序运行过程中难免会出现问题,比如传入了不希望的类型导致程序出问题。
  • 在使用泛型类的时候,比如创建泛型类的对象,只需将类型参数替换为实际的类型即可。
  • Scala自动推断泛型类型特性:直接给使用泛型类型的字段赋值时,Scala会自动进行类型推断。
  • 泛型类的定义如下:

    //定义一个泛型类 
    class Stack[T1, T2, T3](name: T1) { 
    var age: T2 = _ 
    var address: T3 = _ 
    def getInfo: Unit = {
      println(s"$name,$age,$address") 
    } 
    }
    
  • 使用上述的泛型类,只需要使用具体的类型代替类型参数即可。

    object GenericityDemo { 
    def main(args: Array[String]): Unit = {
      //创建泛型类对象 
      val stack = new Stack[String, Int, String]("lisi") 
      stack.age = 20 
      stack.address = "北京" 
      stack.getInfo 
    } 
    }
    

    1.2 泛型函数

  • 泛型函数,与泛型类类似,可以给某个函数在声明时指定泛型类型,然后在函数体内,多个变量或者返回值之间,就可以使用泛型类型进行声明,从而对某个特殊的变量,或者多个变量,进行强制性的类型限制。

  • 与泛型类一样,你可以通过给使用了泛型类型的变量传递值来让Scala自动推断泛型的实际类型,也可以在调用函数时,手动指定泛型类型。
  • 案例:卡片售卖机,可以指定卡片的内容,内容可以是String类型或Int类型

    object GenericityFunction { 
    def getCard[T](content: T) = { 
      content match { 
        case content: Int => s"card:$content is Int " 
        case content: String => s"card:$content is String" 
        case _ => s"card:$content" 
      } 
    }
    def main(args: Array[String]): Unit = { 
      println(getCard[String]("hello")) 
      println(getCard(1001)) 
    } 
    }
    

    1.3 协变和逆变

  • Scala的协变和逆变是非常有特色的,完全解决了Java中的泛型的一大缺憾!

  • 举例来说,Java中,如果有Professional是Master的子类,那么Card[Professionnal]是不是Card[Master]的子类?答案是:不是。因此对于开发程序造成了很多的麻烦。
  • 而Scala中,只要灵活使用协变和逆变,就可以解决Java泛型的问题。

**

  • 协变定义形式如:trait List[+T] {}
  • 当类型S是类型A的子类型时,则List[S]也可以认为是List[A}的子类型,即List[S]可以泛化为List[A],也就是被参数化,类型的泛化方向与参数类型的方向是一致的,所以称为协变(covariance)。

  • 逆变定义形式如:trait List[-T] {}

  • 当类型S是类型A的子类型,则Queue[A]反过来可以认为是Queue[S}的子类型,也就是被参数化类型的泛化方向与参数类型的方向是相反的,所以称为逆变(contravariance)。

  • 协变案例:只有大师以及大师级别以下的名片都可以进入会场 ```scala package lagou.cn.part11

//大师 class Master

//专家 class Professor extends Master

//讲师 class Teacher

//这个是协变,Professor是Master的子类,此时Card[Profesor]也是Card[Master]的子类 class Card[+T]

object CovarianceDemo { def enterMeet(card: Card[Master]): Unit = { //只有Card[Master]及其子类Card[Professor]才能进入会场。 println(“欢迎进入会场!”) } def main(args: Array[String]): Unit = { val masterCard = new Card[Master] val professorCard = new Card[Professor] val teacharCard = new Card[Teacher] enterMeet(masterCard) enterMeet(professorCard) //此处就会报错 //enterMeet(teacharCard) } }

<a name="b79851a3"></a>
## 第 2 节 Akka

- Akka是Java虚拟机平台上构建高并发、分布式和容错应用的工具包和运行时。
- Akka用Scala语言编写,同时提供了Scala和Java的开发接口。
- Akka处理并发的方法基于Actor模型,Actor之间通信的唯一机制就是消息传递。

- **Actor**
   - Scala的Actor类似于Java中的多线程编程。
   - 但是不同的是,Scala的Actor提供的模型与多线程有所不同。Scala的Actor尽可能地避免锁和共享状态,从而避免多线程并发时出现资源争用的情况,进而提升多线程编程的性能。
   - Actor可以看作是一个个独立的实体,Actor之间可以通过交换消息的方式进行通信,每个Actor都有自己的收件箱(Mailbox)。
   - 一个Actor收到其他Actor的信息后,根据需要作出各种相应。消息的类型可以是任意的,消息的内容也可以是任意的。

![image.png](https://cdn.nlark.com/yuque/0/2021/png/3013578/1611127798899-bd7c00d2-1580-4d0f-84aa-e88bf8122d57.png#align=left&display=inline&height=489&margin=%5Bobject%20Object%5D&name=image.png&originHeight=978&originWidth=1486&size=495258&status=done&style=none&width=743)

- **ActorSystem**
   - 在Akka中,ActorSystem是一个重量级的结构。
   - 它需要分配多个线程,所以在实际应用中,ActorSystem通常是一个单例对象,我们可以使用这个ActorSystem创建很多Actor。

- **Akka案例** :
   - 创建一个maven项目,在项目的pom文件中增加如下依赖:
```xml
<!-- 定义一下常量-->
<properties> 
  <encoding>UTF-8</encoding> 
  <scala.version>2.12.3</scala.version> 
  <scala.compat.version>2.11</scala.compat.version> 
  <akka.version>2.4.17</akka.version> 
</properties> 
<dependencies> 
  <!-- 添加akka的actor依赖 -->
  <dependency>
    <groupId>org.scala-lang</groupId> 
    <artifactId>scala-actors</artifactId> 
    <version>2.11.8</version> 
  </dependency> 

  <!-- https://mvnrepository.com/artifact/com.typesafe.akka/akka-actor -->
  <dependency> 
    <groupId>com.typesafe.akka</groupId> 
    <artifactId>akka-actor_2.11</artifactId> 
    <version>2.3.16</version> 
  </dependency>

  <!-- 添加akka的actor依赖 --> 
  <dependency>
    <groupId>com.typesafe.akka</groupId> 
    <artifactId>akka-actor_${scala.compat.version}</artifactId>
    <version>${akka.version}</version>
  </dependency> 

  <!-- 多进程之间的Actor通信 --> 
  <!-- https://mvnrepository.com/artifact/com.typesafe.akka/akka-remote --> 
    <dependency> 
    <groupId>com.typesafe.akka</groupId> 
    <artifactId>akka-remote_${scala.compat.version}</artifactId> 
    <version>${akka.version}</version> 
  </dependency>
</dependencies>
package cn.lagou.edu.scala.section4 
import akka.actor.{Actor, ActorSystem, Props} 
import scala.io.StdIn 

class HelloActor extends Actor { 
  // 接收消息并处理 
  override def receive: Receive = { 
    case "吃了吗" => println("吃过了") 
    case "吃的啥" => println("北京卤煮") 
    case "拜拜" => {
      //关闭自己 
      context.stop(self) 
      //关闭ActorSystem 
      context.system.terminate() 
    } 
  } 
}

object HelloActor { 
  //创建线程池对象MyFactor,myFactory为线程池的名称 
  private val MyFactory = ActorSystem("myFactory") 
  // 通过MyFactory.actorOf方法来创建一个actor; 
  // 第一个参数传递自定义的HelloActor类,第二个参数是给actor起个名字 
  private val helloActorRef = MyFactory.actorOf(Props[HelloActor], "helloActor") 

  def main(args: Array[String]): Unit = { 
    var flag = true while (flag) { 
      print("请输入想发送的消息:") 
      val consoleLine: String = StdIn.readLine() 
      //通过!来发送消息
      helloActorRef ! consoleLine 
      if (consoleLine.equals("拜拜")) {
        flag = false 
        println("程序即将结束!") 
      }
      // 休眠100毫秒 
      Thread.sleep(100) 
    } 
  } 
}