使用Scala实现Java项目的单词计数:串行及Actor版本
其实我想找一门“具有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
- package scalastudy.basic
- import scala.collection.immutable.List
- import scala.collection.mutable
- import scala.collection.mutable.{ArrayBuffer, Map, HashMap}
- import java.io.File
- /**
- * Created by lovesqcc on 16-3-19.
- */
- object WordStat extends App {
- var seps = " -!\"#$%&()*,./:;?@[]^_`{|}~+<=>\\".toArray
- launch()
- def init(): Unit = {
- if (WordStat.seps == null) {
- seps = " -!\"#$%&()*,./:;?@[]^_`{|}~+<=>\\".toArray
- }
- }
- def launch(): Unit = {
- val path = "/home/lovesqcc/work/java/ALLIN/src/main/java/"
- val files = fetchAllJavaFiles(path)
- //files.foreach { println }
- val allWords = files.map(readFile(_)).map(analysisWords(_)).flatten.toList
- sortByValue(statWords(allWords)).map(println)
- }
- def fileJavaFile(filename:String, suffix:String): Boolean = {
- return filename.endsWith(suffix)
- }
- def fetchAllJavaFiles(path:String): Array[String] = {
- val javaFilesBuf = ArrayBuffer[String]()
- fetchJavaFiles(path, javaFilesBuf)
- return javaFilesBuf.toArray
- }
- def fetchJavaFiles(path:String, javafiles:ArrayBuffer[String]):Unit = {
- val dirAndfiles = new File(path).listFiles
- if (dirAndfiles!=null && dirAndfiles.length > 0) {
- val files = dirAndfiles.filter(_.isFile)
- if (files.length > 0) {
- javafiles ++= files.map(_.getCanonicalPath).filter(fileJavaFile(_,".java"))
- }
- val dirs = dirAndfiles.filter(_.isDirectory)
- if (dirs.length > 0) {
- dirs.map(_.getCanonicalPath).foreach { dirpath =>
- fetchJavaFiles(dirpath, javafiles) }
- }
- }
- }
- def readFile(filename:String): String = {
- import scala.io.Source
- val fileSource = Source.fromFile(filename)
- try {
- return fileSource.mkString
- } finally {
- fileSource.close()
- }
- }
- def analysisWords(content:String):List[String] = {
- return splitText(content, WordStat.seps);
- }
- def statWords(words: List[String]):Map[String,Int] = {
- val wordsMap = new HashMap[String,Int]
- words.foreach { w =>
- wordsMap(w) = wordsMap.getOrElse(w,0) + 1
- }
- return wordsMap
- }
- def splitText(text:String, seps:Array[Char]): List[String] = {
- var init = Array(text)
- seps.foreach { sep =>
- init = init.map(_.split(sep)).flatten.map(_.trim).filter(s => s.length > 0)
- }
- return init.toList
- }
- def sortByValue(m: Map[String,Int]): Map[String,Int] = {
- val sortedm = new mutable.LinkedHashMap[String,Int]
- m.toList.sortWith{case(kv1,kv2) => kv1._2 > kv2._2}.foreach { t =>
- sortedm(t._1) = t._2
- }
- return sortedm
- }
- }
初步的 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 程序见后面。
- package scalastudy.concurrent
- import java.lang.Thread
- import akka.actor.{ActorRef, Props, ActorSystem, Actor}
- import akka.actor.Actor.Receive
- import scala.collection.immutable.List
- import scala.collection.mutable
- import scala.collection.mutable.{ArrayBuffer, Map, HashMap}
- import java.io.File
- import scalastudy.basic.WordStat
- /**
- * Created by lovesqcc on 16-3-19.
- */
- object ConcurrentWordStat extends App {
- val seps = " -!\"#$%&()*,./:;?@[]^_`{|}~+<=>\\".toArray
- launch()
- def launch(): Unit = {
- val path = "/home/lovesqcc/work/java/ALLIN/src/main/java/"
- val system = ActorSystem("actor-wordstat")
- val statWordActor = system.actorOf(Props[StatWordActor])
- val analysisWordActor = system.actorOf(Props(new AnalysisWordActor(statWordActor)))
- val readFileActor = system.actorOf(Props(new ReadFileActor(analysisWordActor)))
- val fetchFileActor = system.actorOf(Props(new FetchJavaFileActor(readFileActor)))
- fetchFileActor ! path
- Thread.sleep(6000)
- val concurrentResult:Map[String,Int] = sortByValue(StatWordActor.finalResult())
- WordStat.init()
- val allWords = WordStat.fetchAllJavaFiles(path)
- .map(WordStat.readFile(_))
- .map(WordStat.analysisWords(_)).flatten.toList
- val basicResult:Map[String,Int] = sortByValue(WordStat.statWords(allWords))
- // Compare the results of serial version and actors version
- concurrentResult.keySet.foreach { key =>
- assert(concurrentResult(key) == basicResult(key))
- }
- println("All Passed. Yeah ~~ ")
- system.shutdown
- }
- class FetchJavaFileActor(readFileActor: ActorRef) extends Actor {
- override def receive: Actor.Receive = {
- case path:String =>
- val allJavaFiles:Array[String] = fetchAllJavaFiles(path)
- allJavaFiles.foreach {
- readFileActor ! _
- }
- }
- def fileJavaFile(filename:String, suffix:String): Boolean = {
- return filename.endsWith(suffix)
- }
- def fetchAllJavaFiles(path:String): Array[String] = {
- val javaFilesBuf = ArrayBuffer[String]()
- fetchJavaFiles(path, javaFilesBuf)
- return javaFilesBuf.toArray
- }
- def fetchJavaFiles(path:String, javafiles:ArrayBuffer[String]):Unit = {
- val dirAndfiles = new File(path).listFiles
- if (dirAndfiles!=null && dirAndfiles.length > 0) {
- val files = dirAndfiles.filter(_.isFile)
- if (files.length > 0) {
- javafiles ++= files.map(_.getCanonicalPath).filter(fileJavaFile(_,".java"))
- }
- val dirs = dirAndfiles.filter(_.isDirectory)
- if (dirs.length > 0) {
- dirs.map(_.getCanonicalPath).foreach { dirpath =>
- fetchJavaFiles(dirpath, javafiles) }
- }
- }
- }
- }
- // 记录读取的文件数便于核对
- object ReadFileActor {
- private var fileCount = 0
- private def inc() { fileCount +=1 }
- private def count() = fileCount
- }
- class ReadFileActor(analysisWordActor: ActorRef) extends Actor {
- override def receive: Receive = {
- case filename:String =>
- ReadFileActor.inc()
- println("File count: " + ReadFileActor.count())
- println(filename)
- val content = readFile(filename)
- analysisWordActor ! content
- }
- def readFile(filename:String): String = {
- import scala.io.Source
- val fileSource = Source.fromFile(filename)
- try {
- return fileSource.mkString
- } finally {
- fileSource.close()
- }
- }
- }
- case class WordListWrapper(wordlist: List[String]) {
- def getWordlist = wordlist
- }
- class AnalysisWordActor(statWordActor: ActorRef) extends Actor {
- override def receive: Actor.Receive = {
- case content:String =>
- val words = analysisWords(content)
- statWordActor ! new WordListWrapper(words)
- }
- def analysisWords(content:String):List[String] = {
- return splitText(content, ConcurrentWordStat.seps);
- }
- def splitText(text:String, seps:Array[Char]): List[String] = {
- var init = Array(text)
- seps.foreach { sep =>
- init = init.map(_.split(sep)).flatten.map(_.trim).filter(s => s.length > 0)
- }
- return init.toList
- }
- }
- object StatWordActor {
- var stat:Map[String,Int] = new HashMap[String,Int]
- def add(newstat:Map[String,Int]) = {
- newstat.foreach { e =>
- stat(e._1) = stat.getOrElse(e._1, 0) + newstat.getOrElse(e._1, 0)
- }
- }
- def finalResult() = stat
- private var recvCount = 0
- private def inc() { recvCount +=1 }
- private def count() = recvCount
- }
- class StatWordActor extends Actor {
- override def receive: Actor.Receive = {
- case WordListWrapper(wordlist: List[String]) =>
- StatWordActor.inc()
- println("received times: " + StatWordActor.count())
- val stat:Map[String,Int] = statWords(wordlist)
- StatWordActor.add(stat)
- }
- def statWords(words: List[String]):Map[String,Int] = {
- val wordsMap = new HashMap[String,Int]
- words.foreach { w =>
- wordsMap(w) = wordsMap.getOrElse(w,0) + 1
- }
- return wordsMap
- }
- }
- def sortByValue(m: Map[String,Int]): Map[String,Int] = {
- val sortedm = new mutable.LinkedHashMap[String,Int]
- m.toList.sortWith{case(kv1,kv2) => kv1._2 > kv2._2}.foreach { t =>
- sortedm(t._1) = t._2
- }
- return sortedm
- }
- }
在 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 依赖。
- <dependency>
- <groupId>org.scala-lang</groupId>
- <artifactId>scala-library</artifactId>
- <version>2.11.8</version>
- </dependency>
- <dependency>
- <groupId>com.typesafe.akka</groupId>
- <artifactId>akka-actor_2.11</artifactId>
- <version>2.4.2</version>
- </dependency>
如果是 java7, 那么 jdk7 + <scala-library2.11.8 + akka-actor_2.11(2.3.14)>
- <dependency>
- <groupId>org.scala-lang</groupId>
- <artifactId>scala-library</artifactId>
- <version>2.11.8</version>
- </dependency>
- <dependency>
- <groupId>com.typesafe.akka</groupId>
- <artifactId>akka-actor_2.11</artifactId>
- <version>2.3.14</version>
- </dependency>
库的配置:
- <repositories>
- <repository>
- <id>typesafe</id>
- <name>Typesafe Repository</name>
- <url>http://repo.typesafe.com/typesafe/releases/</url>
- </repository>
- </repositories>
Scala-maven 插件配置:
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-surefire-plugin</artifactId>
- <version>2.8.1</version>
- <configuration>
- <includes>
- <include>**/*.java</include>
- <include>**/*.scala</include>
- </includes>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.scala-tools</groupId>
- <artifactId>maven-scala-plugin</artifactId>
- <version>2.15.2</version>
- <executions>
- <execution>
- <id>scala-compile-first</id>
- <phase>process-resources</phase>
- <goals>
- <goal>compile</goal>
- </goals>
- </execution>
- <execution>
- <id>scala-test-compile</id>
- <phase>process-test-resources</phase>
- <goals>
- <goal>testCompile</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
使用Scala实现Java项目的单词计数:串行及Actor版本的更多相关文章
- Scala IDEA for Eclipse里用maven来创建scala和java项目代码环境(图文详解)
这篇博客 是在Scala IDEA for Eclipse里手动创建scala代码编写环境. Scala IDE for Eclipse的下载.安装和WordCount的初步使用(本地模式和集群模式) ...
- 用maven来创建scala和java项目代码环境(图文详解)(Intellij IDEA(Ultimate版本)、Intellij IDEA(Community版本)和Scala IDEA for Eclipse皆适用)(博主推荐)
不多说,直接上干货! 为什么要写这篇博客? 首先,对于spark项目,强烈建议搭建,用Intellij IDEA(Ultimate版本),如果你还有另所爱好尝试Scala IDEA for Eclip ...
- Scala:Java 项目中混入scala代码
Spark 是用Scala代码写的.为了调试Spark,做了如下尝试. 1.Eclipse下:Java 项目 ,Using Maven,编写了一个java 版Spark应用. Spark的代码(sca ...
- Spark:用Scala和Java实现WordCount
http://www.cnblogs.com/byrhuangqiang/p/4017725.html 为了在IDEA中编写scala,今天安装配置学习了IDEA集成开发环境.IDEA确实很优秀,学会 ...
- java面试一日一题:再谈垃圾回收器中的串行、并行、并发
问题:请讲下java中垃圾回收器的串行.并行.并发 分析:该问题主要考察在垃圾回收过程中垃圾回收线程和用户线程的关系 回答要点: 主要从以下几点去考虑, 1.串行.并行.并发的概念 2.如何考虑串行. ...
- Spark: 单词计数(Word Count)的MapReduce实现(Java/Python)
1 导引 我们在博客<Hadoop: 单词计数(Word Count)的MapReduce实现 >中学习了如何用Hadoop-MapReduce实现单词计数,现在我们来看如何用Spark来 ...
- 在Java项目中整合Scala
Scala是一个运行在Java JVM上的面向对象的语言.它支持函数编程,在语法上比Java更加灵活,同时通过Akka库,Scala支持强大的基于Actor的多线程编程.具有这些优势,使得我最近很想在 ...
- [大数据从入门到放弃系列教程]在IDEA的Java项目里,配置并加入Scala,写出并运行scala的hello world
[大数据从入门到放弃系列教程]在IDEA的Java项目里,配置并加入Scala,写出并运行scala的hello world 原文链接:http://www.cnblogs.com/blog5277/ ...
- Maven Java项目添加Scala语言支持
为了在一个普通的使用Maven构建的Java项目中,增加对Scala语言的支持.使得其能够同时编译Java和Scala语言的文件.其实很简单的一件事情,只需要在pom.xml文件中的build部分中的 ...
随机推荐
- python 获得当前路径
先要装载 os模块: import os print os.getcwd() 或者 print os.path.abspath(os.curdir) print os.path.abspath('.' ...
- 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 ...
- hdu-acm steps 命运
/*表示刚刚接触dp.这是我接触到的第3道dp题,刚开始以为是要用dfs+dp,后来栈溢出...仔细想想, 其实这道题和数塔差不多,只要每步都得到最优子结构,最后结果一定是最优的.题目的初始化要做好, ...
- c语言.大数的输出
转化成字符串,再用for循环输出: #include <stdio.h>#include <string.h>int main(){ char s[32]; int d, ...
- windows下安装redis以及测试
Window 下安装 下载地址:https://github.com/dmajkic/redis/downloads. 下载到的Redis支持32bit和64bit.根据自己实际情况选择,将64bit ...
- PHP第三方登录 -- 微博登录
进化史 博客园 首页 新随笔 联系 管理 订阅 随笔- 9 文章- 0 评论- 0 php 实现qq第三方登录 学习之前,请大家先看一下oAuth协议. 首先呢,我们进入QQ互联的官方网站 ht ...
- Java虚拟机学习记录
一.java平台无关性的基础 1.和各个平台有关的虚拟机: 2.和各个平台无关的中间语言(class文件). 二.虚拟机语言无关性的基础 1.class文件 三.java虚机机器中java程序的生命周 ...
- ios-高德、百度后台定位并上传服务器
一.配置高德或百度的后台定位框架和代码(略). 二.配置app不被系统kill,定时获取地理位置信息,并上传服务器(AppDelegate里面). 具体代码: 1. - (void)applicati ...
- java 求 两个数的百分比% (转)
int num1 = 7; int num2 = 9; // 创建一个数值格式化对象 NumberFormat numberFormat = NumberFormat.getInstance(); / ...
- js基础细节
js细节 1.所有的全局变量都是window的属性. 语句 var a=1; 等价于 window.a=1; 用 "变量名称" in window 来验证全局变量是否声明. 2.所 ...