最近这几天把Scala学完了,被他的数据处理能力深深的折服。

尤其是WordCount功能,之前接过的一个小外包就有这种要求的分词功能,当时是使用的开源依赖Kumo词库,但是别人的东西怎么实现的说实话真不知道。

后来和同学探讨的过程中了解到Elastic Search是可以实现分词功能的,效果极佳但是资源占用大,对于一个小项目来说可能资源大部分回响ES倾斜,所以虽然有所耳闻但是并没有实际实践。

再后来学了Hadoop框架,了解到WordCount在上面的实现是拆分成Map和Reduce两个阶段,但是其过程主要面向框架中处理,虽然都是Java但是其实很多过程并不清晰(其实是没能力手撸一个)

然后学了Scala,刚好Scala最后也是介绍了WordCount功能,本质上Scala对WordCount的处理也是一个Map、Reduce的过程,但是代码简洁(可读性不强),流程也更清晰。于是想着Java和Scala都是基于JVM,那干脆Java中调用Scala,看能否实现。

Scala代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util
import scala.collection.JavaConverters._ // 格式转换依赖包

class wc {
def WordCount(stringList: util.List[String]): util.List[(String, Int)] = {
val s = stringList.asScala.toList // 格式转换Java.List --> Scala.List
// 1) 将句子切分成单词
val wordList: List[String] = s.flatMap(str => str.split(" "))
// 2) 将相同的单词放置在一起
val wordToWordsMap: Map[String, List[String]] = wordList.groupBy(word => word)
// 3) 对相同的单词进行计数
// (word, list) => (word, count)
val wordToCountMap: Map[String, Int] = wordToWordsMap.map(tuple => (tuple._1, tuple._2.size))
// 4) 对计数完成后的结果进行排序(降序)
val sortList: List[(String, Int)] = wordToCountMap.toList.sortWith {
(left, right) => {
left._2 > right._2
}
}
sortList.asJava // 以Java.List的格式返回
}
}
Java端代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import scala.Tuple2;
// 这里引用了Scala元组
import java.util.*;

public class WCImp {
public static void main(String[] args) {
List<String> sl = new ArrayList<String>();
sl.add("Hello Scala Hbase kafka");
sl.add("Hello Scala Hbase");
sl.add(" Hello Scala");
sl.add("Hello");
System.out.println(sl);

wc word = new wc();
List<Tuple2<String, Object>> s = word.WordCount(sl);
System.out.println(s);
}
}


// 输出结果:
// [(Hello,4), (Scala,3), (Hbase,2), (,1), (kafka,1)]

关于这个Tuple元组类型,Java中没有原生支持元组,所以还是导入了一个Scala下的依赖包实现Java端数据的传输,这里是为了方便将传回的数据再在Java中进行处理,如果直接作为结果输出可以不需要在Scala那边转Java格式。

如果还想全部变成Java的话可以尝试在Scala那边将tuple转成map或者其他之后再传,但个人认为这个WordCount的例子中并没有必要。同时,在Java中引入Tuple2后也能实现Scala的元组操作,我觉得挺好玩的(但真不一定很方便)

关于Scala的数据类型,Java中一切类继承自Object类,所以String自然也是Object的子类;但是Scala中略有不同(参照博客scala中的数据类型),Scala的基本数据类型继承自AnyVal,但是String,我们通过源码:

1
2
3
4
5
6
7
8
9
package java.lang;

import java.io.ObjectStreamField;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.*;

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence

索引自String.java,所以说Scala中的String和Java中的String基本上是一样的(甚至跳转到的源码也是一样的)。作为参照,可以参考Scala其他数据类型的源码(以Int为例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package scala

// Int.scala
final abstract class Int private extends AnyVal {
def toByte: Byte
def toShort: Short
def toChar: Char
def toInt: Int
def toLong: Long
def toFloat: Float
def toDouble: Double
}

// AnyVal.scala
abstract class AnyVal extends Any {
def getClass(): Class[_ <: AnyVal] = null
}

这样就能很好的解释为什么在Scala和Java进行数据类型转换的时候String只能转String,而其他的都是AnyVal转Object了。

回到Java的Tuple2中,应该对于Tuple包有相应的包处理,但是我只是一时兴起,并没有过多深入,所以只是浅浅的在Java中对Tuple进行一个输出观察,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (Tuple2 i: s) {
System.out.println("i =" + i);
System.out.println("i.toString =" + i.toString());
System.out.println("Normal: " + i._1 + " " + i._2);
String s1 = (String) i._1;
Integer s2 = (Integer) i._2;
System.out.println("Change: " + s1 + " " + s2);
}

// 输出结果:
// i =(Hello,4)
// i.toString =(Hello,4)
// Normal: Hello 4
// Change: Hello 4

可以看出,输出结果在Java能够正常使用,用getClass函数查看:

1
2
3
4
5
6
System.out.println("Normal: " + i._1.getClass() + " " +  i._2.getClass());
System.out.println("Normal: " + i._1.getClass() + " " + i._2.getClass());

// 输出结果:
// Normal: class java.lang.String class java.lang.Integer
// Change: class java.lang.String class java.lang.Integer

但是注意!虽然其类型都是Java.lang.*,但是tuple中的仍然属于Object类型,需要转换。

本来还想做个Scala、Java转Json和读取Json的测试,但是需要引入其他的依赖,暂时先进行搁置。


参考资料:

为什么String与Int,Boolean,Byte …在scala中不同?

将Java集合转换为Scala集合

将Java集合转换为Scala集合