其实我想找一门“具有Python的简洁写法和融合Java平台的优势, 同时又足够有挑战性和灵活性”的编程语言。 Scala 就是一个不错的选择。 Scala 有很多语言特性, 建议先掌握基础常用的: 变量、控制结构 、正则与模式匹配、集合、文件读写/目录遍历、高阶函数、并发 Actor 模型; 然后是面向对象特性:类、Trait、泛型、注解 、操作符重载;  最后再细细学习那些复杂不常用的特性:类型转换、编译解析等;注重挖掘根源性的思想,能够推导出其它的特性。

 本文使用 Scala 实现 Java 项目的单词计数,采用了顺序编程模型; 后面给出了 Actor 模型的基本实现。

Scala 常用特性:

 1.  与 Java 库方便地互操作;能够使用在 Java Maven 项目中, 只要配置好相应的 maven 依赖包和插件即可;

 2.  不必写分号,脚本特性; 如果多个语句在同一行,则必须写分号加以区分;

 3.  变量声明为 var, 不变量声明为 val ; 优先考虑不变性;

 4.  属性在前,类型在后; “先思考数据模型,后确定数据类型” 的思想;

 5.  静态类型,具备类型推导; 既有工程特性,又兼具脚本能力;

 6.  函数无返回值定义为 Unit , “通吃型”类型 定义为 Any: var typechangeable: Any ; 后续可赋为数值,亦可赋值为字符串或集合;

7.  无参函数调用可以只写函数名,不必写();

8.  匿名函数可以写成 (A,B,...,Z) => exe(A,B,...,Z) ;  函数式编程风格有种数学的严谨和优雅;

9.  常用集合: Array, ArrayBuffer, mutable.List, immutable.List, mutable.Map, mutable.HashMap, immutable.HashMap, Tuple,

        mutable.Set, immutable.Set, mutable.HashSet, immutable.HashSet  集合有很多方便的工具和方法可使用,可相互转化;

访问 Array 或 Map , 使用 array(0) = "xxx" 或 map("key") = "value" ; 访问 Tuple 使用 ._1, ._2, ... 第一个索引为 1 !

 9.  使用集合时必须先指明是 mutable 还是 immutable ; 一般函数返回值使用 immutable, 局部变量使用 mutable ;

10.  集合添加元素使用 += ,  集合连接集合使用 ++= ;

11.  使用 map, filter 方法对集合映射或过滤处理, 传递给函数的参数名为 _ ; 组合起来很强大!

12.  使用 collection.foreach { e => exe(e) } 或 for(e <- collection) { exe(e) } 进行遍历处理;我更喜欢第一种写法; 

13.  使用 object YourAPP extends App { //code } 实现 Main 函数,直接在块 //code 里写主流程,就像 Python 一样;

14.  import , 嵌套类、函数、trait 等可以出现的地方很灵活;

15.  class MyClass(f1: FType, f2:FType2) extends XXX 可定义类的构造器,相当于声明了属性 f1, f2 的 JavaBean;

16.  使用类的伴生对象来计数和一些操作; 类的伴生对象用于存储类的单例以及静态成员及静态方法;

17.  使用 actor ! messageString 或 actor ! object 向 actor 发送信息,就好比使用 obj.dosome 要求对象执行某动作一样;

18.  若要发送的消息比较复杂,可以包装成一个类。 case class MessageWrapper(params ...) { } ;  使用 case MessageWrapper(params ...) => 来接收和使用该类型的消息。

19.  多看文档,少造轮子。

   串行版本

 结构很清晰: 获取指定目录的所有 Java 文件  -> 读取所有 java 文件 => 解析出所有单词列表 => 统计所有单词。基本上常见的集合(Array, ArrayBuffer, List, Map, Tuple ) 都用到了。注意到,通过使用 map 方法, 形成连锁调用, 这种风格还是很流畅的~~ WordStat.init 方法主要用于在其它类中调用以初始化 WordStat.seps , 比如后面的 Actor 并发版本。如果只是直接调用 WordStat 的方法, WordStat.seps 会被初始化为 null

  1. package scalastudy.basic
  2.  
  3. import scala.collection.immutable.List
  4. import scala.collection.mutable
  5. import scala.collection.mutable.{ArrayBuffer, Map, HashMap}
  6. import java.io.File
  7.  
  8. /**
  9. * Created by lovesqcc on 16-3-19.
  10. */
  11. object WordStat extends App {
  12.  
  13. var seps = " -!\"#$%&()*,./:;?@[]^_`{|}~+<=>\\".toArray
  14.  
  15. launch()
  16.  
  17. def init(): Unit = {
  18. if (WordStat.seps == null) {
  19. seps = " -!\"#$%&()*,./:;?@[]^_`{|}~+<=>\\".toArray
  20. }
  21. }
  22.  
  23. def launch(): Unit = {
  24.  
  25. val path = "/home/lovesqcc/work/java/ALLIN/src/main/java/"
  26. val files = fetchAllJavaFiles(path)
  27. //files.foreach { println }
  28.  
  29. val allWords = files.map(readFile(_)).map(analysisWords(_)).flatten.toList
  30. sortByValue(statWords(allWords)).map(println)
  31. }
  32.  
  33. def fileJavaFile(filename:String, suffix:String): Boolean = {
  34. return filename.endsWith(suffix)
  35. }
  36.  
  37. def fetchAllJavaFiles(path:String): Array[String] = {
  38. val javaFilesBuf = ArrayBuffer[String]()
  39. fetchJavaFiles(path, javaFilesBuf)
  40. return javaFilesBuf.toArray
  41. }
  42.  
  43. def fetchJavaFiles(path:String, javafiles:ArrayBuffer[String]):Unit = {
  44. val dirAndfiles = new File(path).listFiles
  45. if (dirAndfiles!=null && dirAndfiles.length > 0) {
  46. val files = dirAndfiles.filter(_.isFile)
  47. if (files.length > 0) {
  48. javafiles ++= files.map(_.getCanonicalPath).filter(fileJavaFile(_,".java"))
  49. }
  50.  
  51. val dirs = dirAndfiles.filter(_.isDirectory)
  52. if (dirs.length > 0) {
  53. dirs.map(_.getCanonicalPath).foreach { dirpath =>
  54. fetchJavaFiles(dirpath, javafiles) }
  55. }
  56. }
  57. }
  58.  
  59. def readFile(filename:String): String = {
  60. import scala.io.Source
  61. val fileSource = Source.fromFile(filename)
  62. try {
  63. return fileSource.mkString
  64. } finally {
  65. fileSource.close()
  66. }
  67. }
  68.  
  69. def analysisWords(content:String):List[String] = {
  70. return splitText(content, WordStat.seps);
  71. }
  72.  
  73. def statWords(words: List[String]):Map[String,Int] = {
  74. val wordsMap = new HashMap[String,Int]
  75. words.foreach { w =>
  76. wordsMap(w) = wordsMap.getOrElse(w,0) + 1
  77. }
  78. return wordsMap
  79. }
  80.  
  81. def splitText(text:String, seps:Array[Char]): List[String] = {
  82. var init = Array(text)
  83. seps.foreach { sep =>
  84. init = init.map(_.split(sep)).flatten.map(_.trim).filter(s => s.length > 0)
  85. }
  86. return init.toList
  87. }
  88.  
  89. def sortByValue(m: Map[String,Int]): Map[String,Int] = {
  90. val sortedm = new mutable.LinkedHashMap[String,Int]
  91. m.toList.sortWith{case(kv1,kv2) => kv1._2 > kv2._2}.foreach { t =>
  92. sortedm(t._1) = t._2
  93. }
  94. return sortedm
  95. }
  96.  
  97. }

初步的 Actor 并发版本

1. 角色分工: 从目录获取 Java 文件的 FetchJavaFileActor ; 读取文件的 ReadFileActor ; 从文件中解析单词的 AnalysisWordActor ; 统计单词数目的 StatWordActor 。 由于在串行版本中已经做到很好的复用,因此在角色分工创建 Actor 时,只需要将相应的函数移进去作为方法调用即可。

2. 每个 Actor 完成自己的工作后,会向下一个 Actor 发送消息,因此前面的 Actor 会持有下一个 Actor 的引用。 FetchJavaFileActor -> ReadFileActor

-> AnalysisWordActor -> StatWordActor

不完善的地方: 1.  FetchJavaFileActor 一次性获取所有文件后发送,并发度不高; 2. Actor 终止的方式很简单。 后续改进;3. 消息接收不够健壮。

注: 在 Java Maven 项目中配置可运行 Scala + Akka 程序见后面。

  1. package scalastudy.concurrent
  2.  
  3. import java.lang.Thread
  4.  
  5. import akka.actor.{ActorRef, Props, ActorSystem, Actor}
  6. import akka.actor.Actor.Receive
  7.  
  8. import scala.collection.immutable.List
  9. import scala.collection.mutable
  10. import scala.collection.mutable.{ArrayBuffer, Map, HashMap}
  11. import java.io.File
  12.  
  13. import scalastudy.basic.WordStat
  14.  
  15. /**
  16. * Created by lovesqcc on 16-3-19.
  17. */
  18. object ConcurrentWordStat extends App {
  19.  
  20. val seps = " -!\"#$%&()*,./:;?@[]^_`{|}~+<=>\\".toArray
  21.  
  22. launch()
  23.  
  24. def launch(): Unit = {
  25. val path = "/home/lovesqcc/work/java/ALLIN/src/main/java/"
  26.  
  27. val system = ActorSystem("actor-wordstat")
  28. val statWordActor = system.actorOf(Props[StatWordActor])
  29. val analysisWordActor = system.actorOf(Props(new AnalysisWordActor(statWordActor)))
  30. val readFileActor = system.actorOf(Props(new ReadFileActor(analysisWordActor)))
  31. val fetchFileActor = system.actorOf(Props(new FetchJavaFileActor(readFileActor)))
  32.  
  33. fetchFileActor ! path
  34.  
  35. Thread.sleep(6000)
  36.  
  37. val concurrentResult:Map[String,Int] = sortByValue(StatWordActor.finalResult())
  38.  
  39. WordStat.init()
  40. val allWords = WordStat.fetchAllJavaFiles(path)
  41. .map(WordStat.readFile(_))
  42. .map(WordStat.analysisWords(_)).flatten.toList
  43. val basicResult:Map[String,Int] = sortByValue(WordStat.statWords(allWords))
  44.  
  45. // Compare the results of serial version and actors version
  46. concurrentResult.keySet.foreach { key =>
  47. assert(concurrentResult(key) == basicResult(key))
  48. }
  49. println("All Passed. Yeah ~~ ")
  50.  
  51. system.shutdown
  52.  
  53. }
  54.  
  55. class FetchJavaFileActor(readFileActor: ActorRef) extends Actor {
  56.  
  57. override def receive: Actor.Receive = {
  58. case path:String =>
  59. val allJavaFiles:Array[String] = fetchAllJavaFiles(path)
  60. allJavaFiles.foreach {
  61. readFileActor ! _
  62. }
  63. }
  64.  
  65. def fileJavaFile(filename:String, suffix:String): Boolean = {
  66. return filename.endsWith(suffix)
  67. }
  68.  
  69. def fetchAllJavaFiles(path:String): Array[String] = {
  70. val javaFilesBuf = ArrayBuffer[String]()
  71. fetchJavaFiles(path, javaFilesBuf)
  72. return javaFilesBuf.toArray
  73. }
  74.  
  75. def fetchJavaFiles(path:String, javafiles:ArrayBuffer[String]):Unit = {
  76. val dirAndfiles = new File(path).listFiles
  77. if (dirAndfiles!=null && dirAndfiles.length > 0) {
  78. val files = dirAndfiles.filter(_.isFile)
  79. if (files.length > 0) {
  80. javafiles ++= files.map(_.getCanonicalPath).filter(fileJavaFile(_,".java"))
  81. }
  82.  
  83. val dirs = dirAndfiles.filter(_.isDirectory)
  84. if (dirs.length > 0) {
  85. dirs.map(_.getCanonicalPath).foreach { dirpath =>
  86. fetchJavaFiles(dirpath, javafiles) }
  87. }
  88. }
  89. }
  90. }
  91.  
  92. // 记录读取的文件数便于核对
  93. object ReadFileActor {
  94. private var fileCount = 0
  95. private def inc() { fileCount +=1 }
  96. private def count() = fileCount
  97. }
  98.  
  99. class ReadFileActor(analysisWordActor: ActorRef) extends Actor {
  100.  
  101. override def receive: Receive = {
  102. case filename:String =>
  103.  
  104. ReadFileActor.inc()
  105. println("File count: " + ReadFileActor.count())
  106. println(filename)
  107.  
  108. val content = readFile(filename)
  109. analysisWordActor ! content
  110. }
  111.  
  112. def readFile(filename:String): String = {
  113. import scala.io.Source
  114. val fileSource = Source.fromFile(filename)
  115. try {
  116. return fileSource.mkString
  117. } finally {
  118. fileSource.close()
  119. }
  120. }
  121. }
  122.  
  123. case class WordListWrapper(wordlist: List[String]) {
  124. def getWordlist = wordlist
  125. }
  126.  
  127. class AnalysisWordActor(statWordActor: ActorRef) extends Actor {
  128.  
  129. override def receive: Actor.Receive = {
  130. case content:String =>
  131. val words = analysisWords(content)
  132. statWordActor ! new WordListWrapper(words)
  133. }
  134.  
  135. def analysisWords(content:String):List[String] = {
  136. return splitText(content, ConcurrentWordStat.seps);
  137. }
  138.  
  139. def splitText(text:String, seps:Array[Char]): List[String] = {
  140. var init = Array(text)
  141. seps.foreach { sep =>
  142. init = init.map(_.split(sep)).flatten.map(_.trim).filter(s => s.length > 0)
  143. }
  144. return init.toList
  145. }
  146. }
  147.  
  148. object StatWordActor {
  149. var stat:Map[String,Int] = new HashMap[String,Int]
  150. def add(newstat:Map[String,Int]) = {
  151. newstat.foreach { e =>
  152. stat(e._1) = stat.getOrElse(e._1, 0) + newstat.getOrElse(e._1, 0)
  153. }
  154. }
  155. def finalResult() = stat
  156.  
  157. private var recvCount = 0
  158. private def inc() { recvCount +=1 }
  159. private def count() = recvCount
  160. }
  161.  
  162. class StatWordActor extends Actor {
  163.  
  164. override def receive: Actor.Receive = {
  165. case WordListWrapper(wordlist: List[String]) =>
  166. StatWordActor.inc()
  167. println("received times: " + StatWordActor.count())
  168. val stat:Map[String,Int] = statWords(wordlist)
  169. StatWordActor.add(stat)
  170. }
  171.  
  172. def statWords(words: List[String]):Map[String,Int] = {
  173. val wordsMap = new HashMap[String,Int]
  174. words.foreach { w =>
  175. wordsMap(w) = wordsMap.getOrElse(w,0) + 1
  176. }
  177. return wordsMap
  178. }
  179. }
  180.  
  181. def sortByValue(m: Map[String,Int]): Map[String,Int] = {
  182. val sortedm = new mutable.LinkedHashMap[String,Int]
  183. m.toList.sortWith{case(kv1,kv2) => kv1._2 > kv2._2}.foreach { t =>
  184. sortedm(t._1) = t._2
  185. }
  186. return sortedm
  187. }
  188.  
  189. }

  

 在 Java Maven 项目中正常运行 Scala + AKKA 编写的程序 

主要是 Java 版本 + Scala 版本 + AKKA 版本 三者要兼容,版本关系参考 http://akka.io/downloads/ ;先确定 java 版本,然后确定 akka 版本,最后选择 Scala 版本。 scala-library 的版本 2.11.x 必须与akka artifactId 版本 保持一致 ! 比如 akka artifactId 版本是 <artifactId>akka-actor_2.11</artifactId>,那么 Scala 版本必须是 2.11.x 。

如果是 java8, 那么 jdk8 + <scala-library2.11.8 + akka-actor_2.11(2.4.2)> 后面两个是 maven 依赖。

  1. <dependency>
  2. <groupId>org.scala-lang</groupId>
  3. <artifactId>scala-library</artifactId>
  4. <version>2.11.8</version>
  5. </dependency>
  6.  
  7. <dependency>
  8. <groupId>com.typesafe.akka</groupId>
  9. <artifactId>akka-actor_2.11</artifactId>
  10. <version>2.4.2</version>
  11. </dependency> 

如果是 java7, 那么 jdk7 + <scala-library2.11.8 + akka-actor_2.11(2.3.14)>

  1. <dependency>
  2. <groupId>org.scala-lang</groupId>
  3. <artifactId>scala-library</artifactId>
  4. <version>2.11.8</version>
  5. </dependency>
  6.  
  7. <dependency>
  8. <groupId>com.typesafe.akka</groupId>
  9. <artifactId>akka-actor_2.11</artifactId>
  10. <version>2.3.14</version>
  11. </dependency>

  库的配置:

  1. <repositories>
  2. <repository>
  3. <id>typesafe</id>
  4. <name>Typesafe Repository</name>
  5. <url>http://repo.typesafe.com/typesafe/releases/</url>
  6. </repository>
  7. </repositories>

Scala-maven 插件配置:

  1. <plugin>
  2. <groupId>org.apache.maven.plugins</groupId>
  3. <artifactId>maven-surefire-plugin</artifactId>
  4. <version>2.8.1</version>
  5. <configuration>
  6. <includes>
  7. <include>**/*.java</include>
  8. <include>**/*.scala</include>
  9. </includes>
  10. </configuration>
  11. </plugin>
  12. <plugin>
  13. <groupId>org.scala-tools</groupId>
  14. <artifactId>maven-scala-plugin</artifactId>
  15. <version>2.15.2</version>
  16. <executions>
  17. <execution>
  18. <id>scala-compile-first</id>
  19. <phase>process-resources</phase>
  20. <goals>
  21. <goal>compile</goal>
  22. </goals>
  23. </execution>
  24. <execution>
  25. <id>scala-test-compile</id>
  26. <phase>process-test-resources</phase>
  27. <goals>
  28. <goal>testCompile</goal>
  29. </goals>
  30. </execution>
  31. </executions>
  32. </plugin>

使用Scala实现Java项目的单词计数:串行及Actor版本的更多相关文章

  1. Scala IDEA for Eclipse里用maven来创建scala和java项目代码环境(图文详解)

    这篇博客 是在Scala IDEA for Eclipse里手动创建scala代码编写环境. Scala IDE for Eclipse的下载.安装和WordCount的初步使用(本地模式和集群模式) ...

  2. 用maven来创建scala和java项目代码环境(图文详解)(Intellij IDEA(Ultimate版本)、Intellij IDEA(Community版本)和Scala IDEA for Eclipse皆适用)(博主推荐)

    不多说,直接上干货! 为什么要写这篇博客? 首先,对于spark项目,强烈建议搭建,用Intellij IDEA(Ultimate版本),如果你还有另所爱好尝试Scala IDEA for Eclip ...

  3. Scala:Java 项目中混入scala代码

    Spark 是用Scala代码写的.为了调试Spark,做了如下尝试. 1.Eclipse下:Java 项目 ,Using Maven,编写了一个java 版Spark应用. Spark的代码(sca ...

  4. Spark:用Scala和Java实现WordCount

    http://www.cnblogs.com/byrhuangqiang/p/4017725.html 为了在IDEA中编写scala,今天安装配置学习了IDEA集成开发环境.IDEA确实很优秀,学会 ...

  5. java面试一日一题:再谈垃圾回收器中的串行、并行、并发

    问题:请讲下java中垃圾回收器的串行.并行.并发 分析:该问题主要考察在垃圾回收过程中垃圾回收线程和用户线程的关系 回答要点: 主要从以下几点去考虑, 1.串行.并行.并发的概念 2.如何考虑串行. ...

  6. Spark: 单词计数(Word Count)的MapReduce实现(Java/Python)

    1 导引 我们在博客<Hadoop: 单词计数(Word Count)的MapReduce实现 >中学习了如何用Hadoop-MapReduce实现单词计数,现在我们来看如何用Spark来 ...

  7. 在Java项目中整合Scala

    Scala是一个运行在Java JVM上的面向对象的语言.它支持函数编程,在语法上比Java更加灵活,同时通过Akka库,Scala支持强大的基于Actor的多线程编程.具有这些优势,使得我最近很想在 ...

  8. [大数据从入门到放弃系列教程]在IDEA的Java项目里,配置并加入Scala,写出并运行scala的hello world

    [大数据从入门到放弃系列教程]在IDEA的Java项目里,配置并加入Scala,写出并运行scala的hello world 原文链接:http://www.cnblogs.com/blog5277/ ...

  9. Maven Java项目添加Scala语言支持

    为了在一个普通的使用Maven构建的Java项目中,增加对Scala语言的支持.使得其能够同时编译Java和Scala语言的文件.其实很简单的一件事情,只需要在pom.xml文件中的build部分中的 ...

随机推荐

  1. python 获得当前路径

    先要装载 os模块: import os print os.getcwd() 或者 print os.path.abspath(os.curdir) print os.path.abspath('.' ...

  2. Logistic Regression vs Decision Trees vs SVM: Part II

    This is the 2nd part of the series. Read the first part here: Logistic Regression Vs Decision Trees ...

  3. hdu-acm steps 命运

    /*表示刚刚接触dp.这是我接触到的第3道dp题,刚开始以为是要用dfs+dp,后来栈溢出...仔细想想, 其实这道题和数塔差不多,只要每步都得到最优子结构,最后结果一定是最优的.题目的初始化要做好, ...

  4. c语言.大数的输出

    转化成字符串,再用for循环输出: #include <stdio.h>#include <string.h>int main(){  char s[32];   int d, ...

  5. windows下安装redis以及测试

    Window 下安装 下载地址:https://github.com/dmajkic/redis/downloads. 下载到的Redis支持32bit和64bit.根据自己实际情况选择,将64bit ...

  6. PHP第三方登录 -- 微博登录

    进化史 博客园 首页 新随笔 联系 管理 订阅 随笔- 9  文章- 0  评论- 0  php 实现qq第三方登录 学习之前,请大家先看一下oAuth协议. 首先呢,我们进入QQ互联的官方网站 ht ...

  7. Java虚拟机学习记录

    一.java平台无关性的基础 1.和各个平台有关的虚拟机: 2.和各个平台无关的中间语言(class文件). 二.虚拟机语言无关性的基础 1.class文件 三.java虚机机器中java程序的生命周 ...

  8. ios-高德、百度后台定位并上传服务器

    一.配置高德或百度的后台定位框架和代码(略). 二.配置app不被系统kill,定时获取地理位置信息,并上传服务器(AppDelegate里面). 具体代码: 1. - (void)applicati ...

  9. java 求 两个数的百分比% (转)

    int num1 = 7; int num2 = 9; // 创建一个数值格式化对象 NumberFormat numberFormat = NumberFormat.getInstance(); / ...

  10. js基础细节

    js细节 1.所有的全局变量都是window的属性. 语句 var a=1; 等价于 window.a=1; 用 "变量名称" in window 来验证全局变量是否声明. 2.所 ...