Scala实战案例---单词计数

Scala Actor并发编程

[注] :Scala Actor是scala 2.10.x版本及以前版本的Actor。Scala在2.11.x版本中将Akka加入其中,作为其默认的Actor,老版本的Actor已经废弃。

1 . 什么是Scala Actor

Scala中的Actor能够实现并行编程的强大功能,它是基于事件模型的并发机制,Scala是运用消息的发送、接收来实现高并发的。

2. Java并行编程与Scala Actor编程的区别

在这里插入图片描述

​ 对于Java,我们都知道它的多线程实现需要对共享资源(变量、对象等)使用synchronized 关键字进行代码块同步、对象锁互斥等等。而且,常常一大块的try…catch语句块中加上wait方法notify方法notifyAll方法是让人很头疼的。原因就在于Java中多数使用的是可变状态的对象资源,对这些资源进行共享来实现多线程编程的话,控制好资源竞争与防止对象状态被意外修改是非常重要的,而对象状态的不变性也是较难以保证的。

​ 与Java的基于共享数据和锁的线程模型不同,Scala的actor包则提供了另外一种不共享任何数据、依赖消息传递的模型,从而进行并发编程。

3. Actor的执行顺序

1、首先调用start()方法启动Actor

2、调用start()方法后其act()方法会被执行

3、向Actor发送消息

4、act方法执行完成之后,程序会调用exit方法

3.1 发送消息的方式

方式 含义
! 发送异步消息,没有返回值。
!? 发送同步消息,等待返回值。
!! 发送异步消息,返回值是 Future[Any]。

注意:Future 表示一个异步操作的结果状态,可能还没有实际完成的异步任务的结果。

Any是所有类的超类,Future[Any]的泛型是异步操作结果的类型

4. Actor实战

4.1 第一个例子 : 实现actor并发编程

1、定义一个class或者是object继承Actor特质,注意导包import scala.actors.Actor

2、重写对应的act方法

3、调用Actor的start方法执行Actor

4、当act方法执行完成,整个程序运行结束

import scala.actors.Actor


class Actor1 extends Actor {
  override def act(): Unit = {
    for (i <- 1 to 5) {
      println(s"Actor1----$i  ")
    }

  }
}

object Actor2 extends Actor{
  override def act(): Unit = {
    for (i <- 1 to 5) {
      println(s"Actor2----$i  ")
    }
     }
}

object Actor1 {
  def main(args: Array[String]): Unit = {
    //1. 创建actor实例
    val actor: Actor1 = new Actor1
    //2. 启动actor
    actor.start()
    Actor2.start()
  }
}

tips : 如果是object继承Actor特质 , 直接调用start()方法即可

说明:上面分别调用了两个单例对象的start()方法,他们的act()方法会被执行,相同与在java中开启了两个线程,线程的run()方法会被执行

注意:这两个Actor是并行执行的,act()方法中的for循环执行完成后actor程序就退出了

4.2 第二个例子 : 实现actor发送、接受消息

1、定义一个class或者是object继承Actor特质,注意导包import scala.actors.Actor

2、重写对应的act方法

3、调用Actor的start方法执行Actor

4、通过不同发送消息的方式对actor发送消息

5、act方法中通过receive方法接受消息并进行相应的处理

6、act方法执行完成之后,程序退出

import scala.actors.Actor


class Actor3 extends Actor {
  override def act(): Unit = {
    //通过receive方法接受消息
    receive{
      case "start" => println("接收到消息了...")
    }
  }
}

object Actor3 {
  def main(args: Array[String]): Unit = {
    //1. 创建actor实例
    val actor: Actor3 = new Actor3
    //2. 启动actor
    actor.start()
    //3. 向actor发送消息
    actor ! "start"
  }
}

tips : 此处我们采用发送异步消息!

4.3 第三个例子 : 实现actor可以不断地接受消息

在act方法中可以使用while(true)的方式,不断的接受消息

import scala.actors.Actor


class Actor4 extends Actor{
  override def act(): Unit = {
    while(true){
      receive{
        case "start" => println("接收开始...")
        case "stop" => println("接收完毕...")
      }
    }
  }
}

object Actor4{
  def main(args: Array[String]): Unit = {
    //1. 创建actor实例
    val actor: Actor4 = new Actor4
    //2. 启动actor
    actor.start()
    //3. 向actor发送消息
    actor ! "start"
    actor ! "stop"
	println("消息发送完成!")
  }
}

在这里插入图片描述

程序会一直运行 , 等待消息的传入

说明:在act()方法中加入了while (true) 循环,就可以不停的接收消息

注意:发送start消息和stop的消息是异步的,但是Actor接收到消息执行的过程是同步的按顺序执行

4.4 第四个例子 : 使用react方法代替receive方法去接受消息

好处:react方式会复用线程,避免频繁的线程创建、销毁和切换。比receive更高效

注意: react 如果要反复执行消息处理,react外层要用loop,不能用while

import scala.actors.Actor


class Actor5 extends Actor {
  override def act(): Unit = {
    loop{
      react{
        case "start" => println("开始接收....")
        case "stop" => println("接收结束....")
      }
    }
  }
}

object Actor5 {
  def main(args: Array[String]): Unit = {
    //1. 创建actor实例
    val actor: Actor5 = new Actor5
    //2. 启动actor
    actor.start()
    //3. 发送消息
    actor ! "start"
    actor ! "start"
    actor ! "stop"
    println("消息发送完成!")
  }
}

4.5 第五个例子 : 结合case class样例类发送消息和接受消息

1、将消息封装在一个样例类中

2、通过匹配不同的样例类去执行不同的操作

3、Actor可以返回消息给发送方。通过sender方法向当前消息发送方返回消息

我们将三种传递消息的方式依次展示如下代码

import scala.actors.{Actor, Future}

//定义样例类
case class AsyMeg(id: Int, msg: String) //异步无返回值消息
case class SynMeg(id: Int, msg: String) //同步消息
case class ReplyMsg(id: Int, msg: String) //返回结果数据

//todo:第五个例子:结合case class 样例类去封装消息
class Actor6 extends Actor {
  override def act(): Unit = {
    loop {
      react {
        //匹配异步消息
        case AsyMeg(id, msg) => {
          println(s"id:$id  msg:$msg")

          //返回结果数据  通过sender方法拿到消息发送方的引用,通过这个引用把消息返回回去
          sender ! ReplyMsg(id, "异步有返回值的消息处理成功")
        }
        case SynMeg(id,msg) =>{
          println(s"id:$id message:$msg")
          //返回结果数据  通过sender方法拿到消息发送方的引用,通过这个引用把消息返回回去
          sender ! ReplyMsg(id, "同步等待返回值的消息处理成功")
        }
      }
    }
  }
}

object Actor6 {
  def main(args: Array[String]): Unit = {
    //1. 创建actor实例
    val actor: Actor6 = new Actor6
    //2. 启动actor
    actor.start()
    //3. 向actor发送消息
    //3.1 发送一个异步无返回值的消息
    actor ! AsyMeg(1, "我是一个异步无返回值的消息")
    //3.2 发送一个异步有返回值的消息
    val future: Future[Any] = actor !! AsyMeg(2, "我是一个异步有返回值的消息")
    val result: Any = future.apply()//可以获取得到future里面的具体内容
    println(result)
    //3.3 发送一个同步等待返回值的消息
    val reply: Any = actor !? SynMeg(3,"我是一个同步等待返回值的消息")
    println(reply)
  }
}
//id:1  msg:我是一个异步无返回值的消息
//id:2  msg:我是一个异步有返回值的消息
//ReplyMsg(2,异步有返回值的消息处理成功)
//id:3 message:我是一个同步等待返回值的消息
//ReplyMsg(3,同步等待返回值的消息处理成功)

tips : 当有返回值的时候 , 用sender进行返回 , 而对于异步有返回值的消息 , 返回的是一个Future类型 , 通过其apply()方法获取返回的内容

Scala实战案例

需求 : 用actor并发编程写一个单机版的WordCount,将多个文件作为输入,计算完成后将多个任务汇总,得到最终的结果。

大致的思想步骤:

1、通过loop +react 方式去不断的接受消息

2、利用case class样例类去匹配对应的操作

3、其中scala中提供了文件读取的接口Source,通过调用其fromFile方法去获取文件内容

4、将每个文件的单词数量进行局部汇总,存放在一个ListBuffer中

5、最后将ListBuffer中的结果进行全局汇总。

1543325891952

准备工作 : 准备多个文件及其内容 , 这里我们以三个文件为例

此三个文件所在路径 : J:\\bigdatatest
aa.txt
hello hadoop spark
spark scala
flume storm
-------------
bb.txt
hadoop hive
spark hello hdfs
flume
-------------
cc.txt
hadoop spark
MapReduce hdfs
flume MapReduce
  • 以单个文件为例 , 计数aa.txt中的单词数
    • 首先创建一个类继承Actor
    • 重写act方法 , 采用loop +react 方式去不断的接受消息
    • 声明一个object
      • 创建actor
      • 启动actor
      • 向actor发送消息 , 由于之后需要汇总结果 , 因此此处定义一个样例类来封装发送的消息给出文件的路径
    • act方法中获取得到文件路径
      • 读取文件内容Source.fromFile()
      • 安装换行符切分 (window下换行符是\r\n linux是 \n mac 是 \r)
      • 按照空格切分,获取所有的单词
      • 每个单词计为1
      • 相同的单词进行分组
      • 获取map中value统计结果
import scala.actors.Actor
import scala.io.Source


//todo : 通过scala中actor来实现多个文件中单词统计的结果
//定义样例类
case class SubmitTask(fileName: String)

class WordTask extends Actor {
  override def act(): Unit = {
    loop {
      react {
        case SubmitTask(fileName) => {
          //1、读取文件内容
          val data: String = Source.fromFile(fileName).mkString
          println(data)
          /** 结果如下
            * hello hadoop spark
            * spark scala
            * flume storm
            * */
          //2、按照换行符切分 window下换行符是\r\n   linux是 \n   mac 是 \r
         // val lines: Array[String] = data.split("\r\n")
          val lines: Array[String] = data.split("\r\n")
          println(lines.toBuffer) //ArrayBuffer(hello hadoop spark, spark scala, flume storm)
          //3、按照空格切分,获取所有的单词
          //val words: Array[String] = lines.map(x => x.split(" ")).flatten
          val words: Array[String] = lines.flatMap(x=>x.split(" "))
          println(words.toBuffer)//(hello, hadoop, spark, spark, scala, flume, storm)

          //4、每个单词计为1
          val wordAndOne: Array[(String, Int)] = words.map(x=>(x,1))
          println(wordAndOne.toBuffer)//((hello,1), (hadoop,1), (spark,1), (spark,1), (scala,1), (flume,1), (storm,1))

          //5、相同的单词进行分组
          val groupByWord: Map[String, Array[(String, Int)]] = wordAndOne.groupBy(x=>x._1)
          for ( (k,v)<- groupByWord) println(k+"----"+v.toBuffer)

          //6、获取map中value统计结果
          val result: Map[String, Int] = groupByWord.mapValues(x=>x.length)
          println(result)//(storm -> 1, hadoop -> 1, spark -> 2, scala -> 1, flume -> 1, hello -> 1)

          //sender
        }
      }
    }
  }
}

object WordCount {
  def main(args: Array[String]): Unit = {
    //1. 创建actor
    val task = new WordTask
    //2. 启动actor
    task.start()
    //3. 向actor发送新消息
    task !! SubmitTask("J:\\bigdatatest\\aa.txt")
  }
}

完整代码

import scala.actors.{Actor, Future}
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.io.Source

//样例类
case class SubmitTask(fileName: String)
case class ResultTask(result:Map[String, Int])

class WordTask extends Actor {
  override def act(): Unit = {
    loop {
      react {
        case SubmitTask(fileName) =>{
          //1. 读取文件内容
          val data: String = Source.fromFile(fileName).mkString
          //println(data)

          //2. 按照换行符切分
          val lines: Array[String] = data.split("\r\n")
          //println(lines.toBuffer)

          //3. 按照空格切分
          //val words: Array[String] = lines.map(x=>x.split(" ")).flatten
          val words: Array[String] = lines.flatMap(x=>x.split(" "))
          //println(words.toBuffer)

          //4. 每个单词计为1
          val wordAndOne: Array[(String, Int)] = words.map(x=>(x,1))
          //println(wordAndOne.toBuffer)

          //5. 相同的单词进行分组
          val groupByWord: Map[String, Array[(String, Int)]] = wordAndOne.groupBy(x=>x._1)
          //for ((k,v) <- groupByWord ) println(k+"..."+v.toBuffer)

          //6. 获取map中value统计结果
          val result: Map[String, Int] = groupByWord.mapValues(x=>x.length)
          //println(result)

          //通过sender将结果数据返回回去
          sender ! ResultTask(result)
        }
      }
    }
  }
}

object WordCount {
  def main(args: Array[String]): Unit = {
    ////定义一个set集合,用于保存每一个文件得到的Future对象
    val futureSet = new mutable.HashSet[Future[Any]]()

    //定义一个list集合,用于保存每一个文件的结果数据 , 由于传回来的数据是ResultTask形式的
    val resultTasksList = new ListBuffer[ResultTask]

    //准备数据文件
    val files=Array("J:\\bigdatatest\\aa.txt","J:\\bigdatatest\\bb.txt","J:\\bigdatatest\\cc.txt")

    //遍历文件数组
    for (f <- files ) {
      //1. 创建actor实例
      val task: WordTask = new WordTask
      //2. 启动actor
      task.start()
      //3. 发送文件路径
      val reply: Future[Any] = task !! SubmitTask(f)
      //把future添加到set集合中
      futureSet += reply
    }

    //因为是异步发送的消息 , 有可能future中还没有数据 , 因此我们先进行判断
    //遍历set集合
    while (futureSet.size>0){
      //过滤出已经有真正数据的future
      //isSet用于判断是否有值
      val completedSet: mutable.HashSet[Future[Any]] = futureSet.filter(x=>x.isSet)
      //遍历有数据的future set集合
      for (c <- completedSet ) {
        //apply用于获取Future中返回的数据内容
        val any: Any = c.apply()
        //添加数据到list集合
        resultTasksList += any.asInstanceOf[ResultTask]

        //为了让while循环有终止条件
        //从futureSet移除掉已经把数据存储在list集中的future
        futureSet -= c
      }
    }
    //println(resultTasksList)
    //ListBuffer(ResultTask(Map(storm -> 1, hadoop -> 1, spark -> 2, scala -> 1, flume -> 1, hello -> 1)), ResultTask(Map(MapReduce -> 2, hadoop -> 1, spark -> 1, flume -> 1, hdfs -> 1)), ResultTask(Map(hadoop -> 1, spark -> 1, hive -> 1, flume -> 1, hello -> 1, hdfs -> 1)))
      //获取到的是3个ResultTask的样例类
      //想要获取其中的Map结果 , 我们使用map方法获取ResultTask中的result字段
      //println(resultTasksList.map(x=>x.result))
      //ListBuffer(Map(hadoop -> 1, spark -> 1, hive -> 1, flume -> 1, hello -> 1, hdfs -> 1), Map(storm -> 1, hadoop -> 1, spark -> 2, scala -> 1, flume -> 1, hello -> 1), Map(MapReduce -> 2, hadoop -> 1, spark -> 1, flume -> 1, hdfs -> 1))
      //对Map进行压缩
      //println(resultTasksList.map(x=>x.result).flatten)
      //ListBuffer((hadoop,1), (spark,1), (hive,1), (flume,1), (hello,1), (hdfs,1), (storm,1), (hadoop,1), (spark,2), (scala,1), (flume,1), (hello,1), (MapReduce,2), (hadoop,1), (spark,1), (flume,1), (hdfs,1))
      //按照键值对中的键分组
      //println(resultTasksList.map(x=>x.result).flatten.groupBy(x=>x._1))
      //Map(MapReduce -> ListBuffer((MapReduce,2)), storm -> ListBuffer((storm,1)), hadoop -> ListBuffer((hadoop,1), (hadoop,1), (hadoop,1)), spark -> ListBuffer((spark,1), (spark,1), (spark,2)), hive -> ListBuffer((hive,1)), scala -> ListBuffer((scala,1)), flume -> ListBuffer((flume,1), (flume,1), (flume,1)), hello -> ListBuffer((hello,1), (hello,1)), hdfs -> ListBuffer((hdfs,1), (hdfs,1)))
      //获取Map中的值并对其中的值求和
      println(resultTasksList.map(x=>x.result).flatten.groupBy(x=>x._1).mapValues(x=>x.foldLeft(0)((x,y)=>x+y._2)))
      //Map(MapReduce -> 2, storm -> 1, hadoop -> 3, spark -> 4, hive -> 1, scala -> 1, flume -> 3, hello -> 2, hdfs -> 2)
  }
}

化简版

import scala.actors.{Actor, Future}
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.io.Source

//样例类
case class SubmitTask(fileName: String)
case class ResultTask(result:Map[String, Int])

class WordTask extends Actor {
  override def act(): Unit = {
    loop {
      react {
        case SubmitTask(fileName) =>{
          //1. 读取文件内容
          val data: String = Source.fromFile(fileName).mkString
          //println(data)

          //2. 按照换行符切分
          val lines: Array[String] = data.split("\r\n")
          //println(lines.toBuffer)

          //3. 按照空格切分
          //val words: Array[String] = lines.map(x=>x.split(" ")).flatten
          val words: Array[String] = lines.flatMap(_.split(" "))
          //println(words.toBuffer)

          //4. 每个单词计为1
          val wordAndOne: Array[(String, Int)] = words.map(x=>(x,1))
          //println(wordAndOne.toBuffer)

          //5. 相同的单词进行分组
          val groupByWord: Map[String, Array[(String, Int)]] = wordAndOne.groupBy(_._1)
          //for ((k,v) <- groupByWord ) println(k+"..."+v.toBuffer)

          //6. 获取map中value统计结果
          val result: Map[String, Int] = groupByWord.mapValues(_.length)
          //println(result)

          //通过sender将结果数据返回回去
          sender ! ResultTask(result)
        }
      }
    }
  }
}

object WordCount {
  def main(args: Array[String]): Unit = {
    ////定义一个set集合,用于保存每一个文件得到的Future对象
    val futureSet = new mutable.HashSet[Future[Any]]()

    //定义一个list集合,用于保存每一个文件的结果数据 , 由于传回来的数据是ResultTask形式的
    val resultTasksList = new ListBuffer[ResultTask]

    //准备数据文件
    val files=Array("J:\\bigdatatest\\aa.txt","J:\\bigdatatest\\bb.txt","J:\\bigdatatest\\cc.txt")

    //遍历文件数组
    for (f <- files ) {
      //1. 创建actor实例
      val task: WordTask = new WordTask
      //2. 启动actor
      task.start()
      //3. 发送文件路径
      val reply: Future[Any] = task !! SubmitTask(f)
      //把future添加到set集合中
      futureSet += reply
    }

    //因为是异步发送的消息 , 有可能future中还没有数据 , 因此我们先进行判断
    //遍历set集合
    while (futureSet.size>0){
      //过滤出已经有真正数据的future
      //isSet用于判断是否有值
      val completedSet: mutable.HashSet[Future[Any]] = futureSet.filter(_.isSet)
      //遍历有数据的future set集合
      for (c <- completedSet ) {
        //apply用于获取Future中返回的数据内容
        val any: Any = c.apply()
        //添加数据到list集合
        resultTasksList += any.asInstanceOf[ResultTask]

        //为了让while循环有终止条件
        //从futureSet移除掉已经把数据存储在list集中的future
        futureSet -= c
      }
    }
    println(resultTasksList.flatMap(_.result).groupBy(_._1).mapValues(_.foldLeft(0)(_+_._2)))
    //println(resultTasksList.map(_.result).flatten.groupBy(_._1).mapValues(_.foldLeft(0)((x,y)=>x+y._2)))
  }
}

猜你喜欢

转载自blog.csdn.net/CoderBoom/article/details/84587667