Java版的Spark大資料中文分詞統計程式完成之後,又經過一周的努力,把Scala版的Spark
大資料中文分詞統計程式也搞出來了,在此分享給各位想學習Spark的朋友。
如下是程式最終啟動並執行介面截圖,和Java版差別不大:
如下是Scala工程結構:
當你在工程主類檔案WordCounter.scala上單擊右鍵,選擇Run As Scala Application:
然後選擇唐詩宋詞進行分詞統計,就會出現前面顯示的分詞結果。
工程代碼已經上傳CSDN:http://download.csdn.net/detail/yangdanbo1975/9608632。
整個工程結構很簡單,Text包中和Java工程中一樣,包含了內建的文字檔。整個工程引用的類庫和Java工程類似,只是多了Scala的內容。
需要注意的是,由於Scala版本的不同, Scala預設引用的類庫也有所不同,例如當選擇Eclipse內建的Scala 2.10.6版本時,swing類庫是自動引
入的,如下圖所示:
可是,如果你選擇不同的Scala版本,比如最新下載安裝的2.1.18版,swing類庫就得自己手動載入了:
你可以在工程屬性的Java Build Path -> Scala Library Container 中Edit Library,來切換Scala Library的版本:
整個工程總共包括GuiUtils.scala,SparkWordCount.scala,TextPane.scala和WordCounter.scala四個Scala類和JavaUtil.java一個Java類。
GuiUtils.scala完全複製自網上代碼,實現了類似 於Java Swing中OptionPanel的message 提示框的功能。
TextPane.scala則複製自GitHub上的ScalaSwing2項目,把JTextPanel移植到了Scala中。標準的Scala Library直到2.1.18版本都沒有實現
TextPanel,只有TextArea,我們的工程中顯示分詞結果沿用了Java版的JTextPane,所以我們複製了這個Scala版的。
SparkWordCount.scala類實現了Spark中文分詞統計的核心功能,是在DT 大資料夢工廠王家林老師的SparkWordCount的代碼基礎上改寫的。
首先,把主要功能步驟從伴生對象的main方法中移到了SparkWordCount類中,並拆分為多個方法,使得伴生對象的main方法和後面的GUI介面
都能調用:
class SparkWordCount {
var sc:SparkContext = null;
def initSpark(appName:String){
/**
* 第1步:建立Spark的設定物件SparkConf,設定Spark程式的運行時的配置資訊,
* 例如說通過setMaster來設定程式要連結的Spark叢集的Master的URL,如果設定
* 為local,則代表Spark程式在本地運行,特別適合於機器配置條件非常差(例如
* 只有1G的記憶體)的初學者 *
*/
val conf = new SparkConf() //建立SparkConf對象
conf.setAppName(appName) //設定應用程式的名稱,在程式啟動並執行監控介面可以看到名稱
conf.setMaster("local") //此時,程式在本地運行,不需要安裝Spark叢集
/**
* 第2步:建立SparkContext對象
* SparkContext是Spark程式所有功能的唯一入口,無論是採用Scala、Java、Python、R等都必須有一個SparkContext
* SparkContext核心作用:初始化Spark應用程式運行所需要的核心組件,包括DAGScheduler、TaskScheduler、SchedulerBackend
* 同時還會負責Spark程式往Master註冊程式等
* SparkContext是整個Spark應用程式中最為至關重要的一個對象
*/
sc = new SparkContext(conf) //建立SparkContext對象,通過傳入SparkConf執行個體來定製Spark啟動並執行具體參數和配置資訊
}
def wordCount(doc:String, wordLength:Int):RDD[(String,Int)]={
/**
* 第3步:根據具體的資料來源(HDFS、HBase、Local FS、DB、S3等)通過SparkContext來建立RDD
* RDD的建立基本有三種方式:根據外部的資料來源(例如HDFS)、根據Scala集合、由其它的RDD操作
* 資料會被RDD劃分成為一系列的Partitions,分配到每個Partition的資料屬於一個Task的處理範疇
*/
//val lines = sc.textFile("E://text//唐詩三百首.txt", 1) //讀取本地檔案並設定為一個Partion
//val lines = sc.textFile("src/com/magicstudio/spark/text/唐詩三百首.txt", 1)
val lines = sc.textFile(doc, 1)
/**
* 第4步:對初始的RDD進行Transformation層級的處理,例如map、filter等高階函數等的編程,來進行具體的資料計算
* 第4.1步:講每一行的字串拆分成單個的單詞
*/
//val words = lines.flatMap { line => line.split(" ")} //對每一行的字串進行單詞拆分並把所有行的拆分結果通過flat合并成為一個大的單詞集合
val words = lines.flatMap { line => JavaUtil.getSplitWords(line, wordLength).asScala }
/**
* 第4步:對初始的RDD進行Transformation層級的處理,例如map、filter等高階函數等的編程,來進行具體的資料計算
* 第4.2步:在單詞拆分的基礎上對每個單詞執行個體計數為1,也就是word => (word, 1)
*/
val pairs = words.map { word => (word, 1) }
/**
* 第4步:對初始的RDD進行Transformation層級的處理,例如map、filter等高階函數等的編程,來進行具體的資料計算
* 第4.3步:在每個單詞執行個體計數為1基礎之上統計每個單詞在檔案中出現的總次數
*/
val wordCounts = pairs.reduceByKey(_+_) //對相同的Key,進行Value的累計(包括Local和Reducer層級同時Reduce)
//added by Dumbbell Yang at 2016-07-24
wordCounts.sortBy(x => x._2 , false, wordCounts.partitions.size)
}
def outputResult(wordCounts:RDD[(String,Int)]){
wordCounts.foreach(wordNumberPair => println(wordNumberPair._1 + " : " + wordNumberPair._2))
}
def closeSpark(){
sc.stop()
}
}
其次,在wordCount方法中,把原來第3步讀取固定檔案的方式改為參數方式,可以是src目錄下的相對檔案路徑(在GUI介面上通過下拉
框選擇),也可以是本地磁碟上的絕對檔案路徑(通過檔案瀏覽框選擇):
//val lines = sc.textFile("E://text//唐詩三百首.txt", 1) //讀取本地檔案並設定為一個Partion
//val lines = sc.textFile("src/com/magicstudio/spark/text/唐詩三百首.txt", 1)
val lines = sc.textFile(doc, 1)
然後就是第4.1步中,通過調用JavaUtil類中的java方法,實現了中文分詞功能,替換掉原來簡單的split,對每一行文本進行中文分詞:
//val words = lines.flatMap { line => line.split(" ")} //對每一行的字串進行單詞拆分並把所有行的拆分結果通過flat合并成為一個大的單詞集合
val words = lines.flatMap { line => JavaUtil.getSplitWords(line, wordLength).asScala }
需要注意的是,由於需要調用Java功能,在Scala和Java之間進行資料傳遞,所以必須引用資料類型轉換的library:
import collection.JavaConverters._
然後,才可以對JavaUtil中的getSplitWords方法返回的結果進行asScala的轉換,使之能夠滿足Scala方法調用的要求。
最後的一個改動,就是加上了一個對分詞統計結果按照詞頻進行排序的功能:
//added by Dumbbell Yang at 2016-07-24
wordCounts.sortBy(x => x._2 , false, wordCounts.partitions.size)
可以對比Java方法實現排序時,交換key和value,排序,然後在交換回去的繁瑣,scala語言確實方便很多。
經過以上改動之後,Spark中文分詞統計功能,既可以從main方法中調用,如伴生對象中原來的調用:
/**
* 使用Scala開發本地測試的Spark WordCount程式
* @author DT大資料夢工廠
* 新浪微博:http://weibo.com/ilovepains/
*/
object SparkWordCount{
def main(args: Array[String]){
val counter = new SparkWordCount
counter.initSpark("Spark中文分詞統計")
val words = counter.wordCount("src/com/magicstudio/spark/text/唐詩三百首.txt", 2)
counter.outputResult(words)
counter.closeSpark()
}
}
也可以從WordCounter.scala這個GUI介面程式中調用。
WordCounter.scala類主要實現了Spark中文分詞統計程式的GUI介面,代碼也並不複雜,需要注意的有以下幾點:
首先伴生對象聲明,最新的Scala Library中,是基於SimpleSwingApplication的:
object WordCounter extends SimpleSwingApplication {
但是在早期Scala Library中,這個類名字是SimpleGUIApplication,所以網上很多沒有及時更新的代碼,在新的Scala
Library下都需要修改類名才能編譯運行。
其次,是關於Scala函數傳回值,文檔上只是說函數最後一個語句的傳回值就是函數的傳回值,但其實並不具體,經過
程式測試,其實應該說是最後一個執行語句的傳回值更確切些,而且應該指出在不同的條件下,會執行不同的邏輯,因而
最後一個執行語句並不是像很多例子中那樣,一定就是語句的最後一行,例如:
def getDocPath():String={
if (docField.text.isEmpty()){
"src/com/magicstudio/spark/text/" + cboDoc.selection.item + ".txt"
}
else{
docField.text
}
}
再例如:
def getTopN():Int={
if (top50.selected){
50
}
else if (top100.selected){
100
}
else if (top500.selected){
500
}
else if (top1000.selected){
1000
}
else if (topAll.selected){
0
}
else{
0
}
}
而且,傳回值不用寫return,直接運算式即可,充分體現了Scala語言孜孜以求的精簡。
最後值得一提的是Scala和Java的相互調用功能,對於複用已有的Java開發的大量應用功能,意義深遠。
在Scala工程中,你可以添加Java類,引用已有的Java類,用java方法實現很多功能,然後在Scala類中來調用,
例如,在本工程中,中文分詞功能就是通過java方法,引用IKAnalyzer組件在JavaUtil方法中實現的,在Scala類中
調用。再例如,JavaUtil中的其他方法,如:
public static void showRDDWordCount(JavaRDD<Tuple2<String, Int>> wordCount,
int countLimit, String curDoc, JTextPane resultPane, JCheckBox chkClear)
也是改寫自原來的Java工程中的源碼,在Scala類中引用,完成在GUI介面顯示分詞結果的功能。
當然,為了在Scala中引用,對參數做了一些改動,如原來沒有傳遞介面控制項,現在改成傳遞Scala介面組件的
peer(對應的Java Swing組件),原來的分詞元組是Tuple2<String,Integer>,現在改成Tuple2<String,Int>,用Scala的
Int類型替換掉Java的Integer類型,因為Scala的RDD.toJavaRDD()方法產生的RDD是<String,Int>。而Java完全可以引
用Scala的Int類型(本來的Tuple2就是Scala的類型)。總而言之,Scala和Java相互調用的功能還是很強大,很方便的。
以上便是對Scala語言實現Spark中文分詞統計的一個小小總結。以後有時間的話,我會繼續嘗試SparkStreaming,
Spark SQL等Spark其它相關技術,爭取全面掌握Spark。