WordCount案例
接下來,我們要使用Actor併發編程模型實現多文件的單詞統計。
案例介紹
給定幾個文本文件(文本文件都是以空格分隔的),使用Actor併發編程來統計單詞的數量
思路分析
實現思路
- MainActor獲取要進行單詞統計的文件
- 根據文件數量創建對應的WordCountActor
- 將文件名封裝爲消息發送給WordCountActor
- WordCountActor接收消息,並統計單個文件的單詞計數
- 將單詞計數結果發送給MainActor
- MainActor等待所有的WordCountActor都已經成功返回消息,然後進行結果合併
步驟1 | 獲取文件列表
實現思路
在main方法中讀取指定目錄(${project_root_dir}/data/)下的所有文件,並打印所有的文件名
實現步驟
- 創建用於測試的數據文件
- 加載工程根目錄,獲取到所有文件
- 將每一個文件名,添加目錄路徑
- 打印所有文件名
參考代碼
// 1. MainActor獲取要進行單詞統計的文件
val DIR_PATH = "./data/"
val dataDir = new File(DIR_PATH)
// 讀取所有data目錄下的所有文件
println("對以下文件進行單詞統計:")
// 構建文件列表
val fileList = dataDir.list().toList.map(DIR_PATH + _)
println(fileList)
步驟2 | 創建WordCountActor
實現思路
根據文件數量創建WordCountActor,爲了方便後續發送消息給Actor,將每個Actor與文件名關聯在一起
實現步驟
- 創建WordCountActor
- 將文件列表轉換爲WordCountActor
- 爲了後續方便發送消息給Actor,將Actor列表和文件列表拉鍊到一起
- 打印測試
參考代碼
MainActor.scala
// 2. 根據文件數量創建對應的WordCountActor
val actorList = fileList.map {
x => new WordCountActor
}
// 將Actor和文件名列表建立爲元組
val actorWithFileList: List[(WordCountActor, String)] = actorList.zip(fileList)
WordCountActor.scala
class WordCountActor extends Actor{
override def act(): Unit = {
}
}
步驟3 | 啓動Actor/發送/接收任務消息
實現思路
啓動所有WordCountActor,併發送單詞統計任務消息給每個WordCountActor
[!NOTE]
此處應發送異步有返回消息
實現步驟
- 創建一個WordCountTask樣例類消息,封裝要進行單詞計數的文件名
- 啓動所有WordCountTask,併發送異步有返回消息
- 獲取到所有的WordCount中獲取到的消息(封裝到一個Future列表中)
- 在WordCountActor中接收並打印消息
參考代碼
MainActor.scala
// 3. 將文件名封裝爲消息發送給WordCountActor,並獲取到異步返回結果
val futureList = actorWithFileList.map {
// tuple爲Actor和文件名
tuple =>
// 啓動actor
tuple._1.start()
// 發送任務消息
tuple._1 !! WordCountTask(tuple._2)
}
MessagePackage.scala
/**
* 單詞統計任務消息
* @param fileName 文件名
*/
case class WordCountTask(fileName:String)
WordCountActor.scala
loop {
receive {
// 接收單詞統計任務消息
case WordCountTask(fileName) => {
println("接收到消息:" + fileName)
}
}
}
步驟4 | 消息統計文件單詞計數
實現思路
讀取文件文本,並統計出來單詞的數量。例如:
(hadoop, 3), (spark, 1)...
實現步驟
- 讀取文件內容,並轉換爲列表
- 按照空格切割文本,並轉換爲一個一個的單詞
- 爲了方便進行計數,將單詞轉換爲元組
- 按照單詞進行分組,然後再進行聚合統計
- 打印聚合統計結果
參考代碼
WordCountActor.scala
// 4. 統計單個文件的單詞計數
val iter: Iterator[String] = Source.fromFile(fileName).getLines()
// [第一行] hadoop hadoop
// [第二行] hadoop spark
val lineList = iter.toList
// [單詞列表] hadoop, hadoop, hadoop, spark
val wordList: List[String] = lineList.flatMap(_.split(" "))
// 將單詞轉換爲元組
// [元組列表] (hadoop, 1), (hadoop, 1), (hadoop, 1), (spark, 1)
val tupleList = wordList.map(_ -> 1)
// 按照單詞進行分組
// [單詞分組] = {hadoop->List(hadoop->1, hadoop->1, hadoop->1), spark->List(spark ->1)}
val grouped: Map[String, List[(String, Int)]] = tupleList.groupBy(_._1)
// 將分組內的數據進行聚合
// [單詞計數] = (hadoop, 3), (spark, 1)
val wordCount: Map[String, Int] = grouped.map {
tuple =>
// 單詞
val word = tuple._1
// 進行計數
// 獲取到所有的單詞數量,然後進行累加
val total = tuple._2.map(_._2).sum
word -> total
}
println(wordCount)
步驟5 | 封裝單詞計數結果回覆給MainActor
實現思路
- 將單詞計數的結果封裝爲一個樣例類消息,併發送給MainActor
- MainActor等待所有WordCount均已返回後獲取到每個WordCountActor單詞計算後的結果
實現步驟
- 定義一個樣例類封裝單詞計數結果
- 將單詞計數結果發送給MainActor
- MainActor中檢測所有WordActor是否均已返回,如果均已返回,則獲取並轉換結果
- 打印結果
參考代碼
MessagePackage.scala
/**
* 單詞統計結果
* @param wordCount 單詞計數
*/
case class WordCountResult(wordCount: Map[String, Int])
WordCountActor.scala
// 5. 將單詞計數結果回覆給MainActor
sender ! WordCountResult(wordCount)
MainActor.scala
// 等待所有Actor都已經返回
while(futureList.filter(_.isSet).size != fileList.size){}
// MainActor等待所有的WordCountActor都已經成功返回消息,然後進行結果合併
val resultList: List[Map[String, Int]] = futureList.map(_.apply.asInstanceOf[WordCountResult].wordCount)
println("接收到所有統計結果:" + resultList)
步驟6 | 結果合併
實現思路
對接收到的所有單詞計數進行合併。因爲該部分已經在WordCountActor已經編寫過,所以抽取這部分一樣的代碼到一個工具類中,再調用合併得到最終結果
實現步驟
- 創建一個用於單詞合併的工具類
- 抽取重複代碼爲一個方法
- 在MainActor調用該合併方法,計算得到最終結果,並打印
參考代碼
WordCountUtil.scala
/**
* 單詞分組統計
* @param wordCountList 單詞計數列表
* @return 分組聚合結果
*/
def reduce(wordCountList:List[(String, Int)]) = {
// 按照單詞進行分組
// [單詞分組] = {hadoop->List(hadoop->1, hadoop->1, hadoop->1), spark->List(spark ->1)}
val grouped: Map[String, List[(String, Int)]] = wordCountList.groupBy(_._1)
// 將分組內的數據進行聚合
// [單詞計數] = (hadoop, 3), (spark, 1)
val wordCount: Map[String, Int] = grouped.map {
tuple =>
// 單詞
val word = tuple._1
// 進行計數
// 獲取到所有的單詞數量,然後進行累加
val total = tuple._2.map(_._2).sum
word -> total
}
wordCount
}
MainActor.scala
// 扁平化後再聚合計算
val result: Map[String, Int] = WordCountUtil.reduce(resultList.flatten)
println("最終結果:" + result)