需求:模拟MapReduce,对磁盘文件(N个)里面的单词进行词频统计(统计每个单词在文件中出现的次数)
区别:计算采用本地模式(单线程),只是模拟Map和Reduce的联合过程,并不单独分离出两个任务(方法)
目的:通过不同语言实现词频统计功能,并对比各自的风格
一、数据样例(Samples)
百度网盘:wordcount.rar
主要是为了测试,所以数据尽量怎么简单怎么来,也可以自定义样例数据
二、思路
三、编程实现
(1) Java demo实现
package com.appleyk.java;
import sun.applet.Main;
import java.io.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Description
* @Author Appleyk
* @Blob https://blog.youkuaiyun.com/appleyk
* @Date Created on 上午 8:44 2018-9-6
*/
public class JWordCount {
// main方法快捷键 : psvm
public static void main(String[] args) throws Exception{
System.out.println("================Java=============");
String path = "F:\\wordcount";
File dirFile = new File(path);
// file数组
File[] files = dirFile.listFiles();
for (File f : files) {
System.out.println(f);
}
// file数组转List
List<File> fileList = Arrays.asList(files);
Map<String,Integer> wordMap = new HashMap<>();
fileList.forEach(file->{
try{
BufferedReader bReader = new BufferedReader(new FileReader(file));
// 循环读取行
String line = "";
while((line=bReader.readLine())!=null){
String[] lines = line.split(" ");
for (String word : lines) {
if(wordMap.containsKey(word)){
int value = wordMap.get(word);
wordMap.put(word, value+=1);
}else{
wordMap.put(word, 1);
}
}
}
bReader.close();//释放资源
}catch (IOException ex){
System.out.println(ex.getMessage());
}
});
System.out.println("=====Java词频统计结果打印输出======");
System.out.println(wordMap);
for(Map.Entry<String,Integer> result:wordMap.entrySet()){
System.out.println("单词:"+result.getKey()+",个数:"+result.getValue());
}
}
}
Java大家都是很熟悉了吧,因此写起来demo毫不费劲,总体下来还是一气呵成的,代码不做过多解释,唯一需要说明的就是demo中采用了Java8的特性 -- forEach(很方便)
我们看下执行的结果:
================Java=============
F:\wordcount\1.txt
F:\wordcount\2.txt
F:\wordcount\3.txt
F:\wordcount\4.txt
=====Java词频统计结果打印输出======
{a=8, b=8, c=12, d=8, e=8, f=4}
单词:a,个数:8
单词:b,个数:8
单词:c,个数:12
单词:d,个数:8
单词:e,个数:8
单词:f,个数:4
(2) Scala demo实现
package com.appleyk.scala
import java.io.File
import scala.io.Source
object SWordCount{
def main(args: Array[String]): Unit = {
System.out.println("================Scala=============")
val path = "F:\\wordcount" //目录url
val dirFile = new File(path) //混搭Java代码,new一个file对象,该对象是一个目录
val files = dirFile.listFiles //列出file目录下的所有文件【Java中是一个file数组】
//遍历files对象【数组】
for(file <- files) println(file)
// 将files对象转化为List集合
val fileList = files.toList
// Scala中使用不可变的映射。如果想使用可变集,必须明确地导入scala.collection.mutable.Map类
// wordMap变量不可变,但是其指向的却是一个可变的Map集合,键值对 --> key:String -> value:Int
val wordMap = scala.collection.mutable.LinkedHashMap[String,Int]()
// 遍历【非常恐怖,三次foreach搞定词频统计!!!】
fileList foreach(file=> Source.fromFile(file).getLines() //从file里面读取二进制流,并获取流的行数据列表
.foreach(line=>line.split(" ") // 每一行数据根据空格分割成words列表
.foreach(word=>{
//判断word【单词】是否在map里
if(wordMap.contains(word)){
wordMap(word)+=1 // 有的话,计数器(key对应的value)+1
}else{
wordMap+=(word -> 1) // 否则的话,将单词word加进map集合中,并初始化value=1
}
})
))
printf("=====C编码风格的输出【词频统计结果打印】======%s","\n")
System.out.println(wordMap)
for((k,v) <- wordMap) printf("单词:%s,个数:%d \n",k,v)
}
}
由于Scala运行于Java平台(Java虚拟机)上,并兼容现有的Java程序,因此,我们在编码的时候也可以混搭着Java的代码;同时,我们对比发现,Scala的编码风格真的很随意,可以【对象 空格 foreach】也可以使用【对象.foreach】的方式进行列表的遍历,同时,其支持lambda表达式【匿名函数】,结合foreach,我们可以利用很少的代码将整个需求给实现出来,而且map集合的for循环打印出key-value键值对也是很轻松的,比较Java,Scala确实很棒,博主在学习Scala语言的时候,发现其和Python的语法有些像,所谓语言都是相通的,学好一门语言,其他语言基本上都能很快入手,不多说,我们看下Scala运行的效果:
================Scala=============
F:\wordcount\1.txt
F:\wordcount\2.txt
F:\wordcount\3.txt
F:\wordcount\4.txt
=====C编码风格的输出【词频统计结果打印】======
Map(a -> 8, b -> 8, c -> 12, d -> 8, e -> 8, f -> 4)
单词:a,个数:8
单词:b,个数:8
单词:c,个数:12
单词:d,个数:8
单词:e,个数:8
单词:f,个数:4
需要说明下,使用Map集合,最后的结果是乱序的,因此demo中使用了LinkedHashMap【有序Map】
(3) Python demo实现
#!/usr/bin/env Python3
# -*- encoding:utf-8 -*-
import os #导入os模块,并使用该模块调用系统命令,获得路径,操作系统的类型等
if __name__=="__main__":
print("================Pyhton=============")
path="F:\\wordcount" #目录url
filenames = os.listdir(path) #os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表,注意是名字
#拿到文件的全路径名称集合
filefullnames = [os.path.join(path,filename) for filename in filenames]
for name in filefullnames:
print(name)
#遍历文件名列表+完整路径拼接+生成文件对象列表
fileList = [open(filefullname) for filefullname in filefullnames]
#上面的生成式的当时等价于下面的for代码块
# for filename in filenames:
# filename = os.path.join(path,filename) #文件完整路径拼接
# fileList.append(open(filename,'r')) #添加文件对象
wordMap = {} #定义一个单词的字典【dict】集合
#遍历文件对象列表
for file in fileList:
for line in file: #读取文件中的每一行
words = line.strip('\n').split(" ") #消除换行符,并对每一行数据进行空格分割
for word in words: #遍历单词列表
if wordMap.get(word) is not None: #如果字典表里面包含了单词word
wordMap[word]+=1 #出现频率+1
else:
wordMap[word]=1 #添加一个单词,初始化频率为1
file.close() #释放资源
print("=====Python词频统计结果打印输出======")
print(wordMap)
for key in wordMap.keys():
print("单词:%s,个数:%d" %(key,wordMap.get(key)))
如果说Scala在对变量的类型没有太大要求的话【val:不可变,var:可变,但是类型是自动匹配的】的话,那么Python对变量简直就是没有要求,你不需要给变量指定什么String类型、Int类型或者是指定变量是val可变的还是var不可变的,其变量的赋值操作就是变量声明和定义的过程,因此,在声明一个变量的时候,必须先赋值
而且Python有列表生成式,可以很方便的根据条件生成我们最终想要的结果列表,如下:
利用列表生成式再结合条件过滤,生成一个偶数序列
demo中,我们利用列表生成式(List generation)的特性拿到了文件的完整路径列表,如下
更多生成式的用法,可以参考我的博文:Python3学习(9)--列表生成式(List generation)
.....................................................话不多说,我们直接运行demo,看一下pyhton的执行情况
================Pyhton=============
F:\wordcount\1.txt
F:\wordcount\2.txt
F:\wordcount\3.txt
F:\wordcount\4.txt
=====Python词频统计结果打印输出======
{'a': 8, 'b': 8, 'c': 12, 'd': 8, 'e': 8, 'f': 4}
单词:a,个数:8
单词:b,个数:8
单词:c,个数:12
单词:d,个数:8
单词:e,个数:8
单词:f,个数:4
四、最后
通过对比,我们可以看出,在代码简洁程度上,Scala和Python那是旗鼓相当啊,但如果只能选一个的话,那非Pyhton莫属了!
而且,Python写起demo来比较嗨皮,,Java则是中规中矩的老大哥,Scala由于是分布式计算框架Spark的实现语言,因此,
Scala我们可以稍微了解一下,语言都是相通的,建议多掌握几门辅助语言,这样有利于日常工作的顺利开展