• 使用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>

        

  • 相关阅读:
    Django+ajax 返回json数据挨个显示在页面及页面和后台相互传值
    pycharm、Django+node.js、vue搭建web项目
    windows10使用npm安装vue、vue-cli
    跟踪路由(tracert)及ping命令
    js中arguments详解
    JavaScript 中 call()、apply()、bind() 的用法
    单点登录 JWT -- JSON WEB TOKEN
    js实现冒泡排序,快速排序
    记一次网页加载优化
    移动页面input手机键盘中的“搜索”按键
  • 原文地址:https://www.cnblogs.com/lovesqcc/p/5297268.html
Copyright © 2020-2023  润新知