Groovy Unique 方法性能测试
最近系统遇到一个问题就是一个调用一个groovy方法总是长时间卡住,研究发现这个方法中使用了unique方法。身为一个java程序员一直比较习惯用HashSet去过滤重复的数据,看到groovy中有这么一个去重复的方法,觉得挺方便,所以就大咧咧的使用了。但是测试以后发现,这个方法性能真的是很差劲的……
数字列表的去重
测试代码
首先是测试数值的比较,我以下代码分别测速list的大小为100, 10000, 1000000。
random = new Random();
performanceReport(100)
performanceReport(10000)
performanceReport(10000000)
def performanceReport(int size) {
println("Report for list size $size")
def list = [];
(1..size).each {
list.add(random.nextInt(1000))
}
def start = System.currentTimeMillis();
list.unique();
def end = System.currentTimeMillis();
def dur = end - start;
dur /= 1000;
println("unique use time $dur s");
list.clear();
(1..size).each {
list.add(random.nextInt(1000))
}
start = System.currentTimeMillis();
def uSet = new HashSet(list.size());
for (e in list) {
uSet.add(e);
}
end = System.currentTimeMillis();
dur = end - start;
dur /= 1000;
println("hash set use time $dur s");
println('---------------------');
}
测试结果
Report for list size 100
unique use time 0.006 s
hash set use time 0.006 s
---------------------
Report for list size 10000
unique use time 0.127 s
hash set use time 0.004 s
---------------------
Report for list size 10000000
unique use time 49.479 s
hash set use time 0.279 s
我重复运行了代码三遍,出来的结果都差不多,所以对比结果基本是确定的:
- List比较少元素的情况下,比如100以内,运行效率差不了多少
- 当List元素较多的时候,差异非常明显,特别是大容量List的运行结果,使用unique就绝对是一个错误的选择!
字符串列表的去重
测试代码
我使用随机生成的十位英文字符串(只包含a、b、c)进行测试比较。测试运行比较列表大小为100、1000、100000的结果。
random = new Random();
strings = ['a', 'b', 'c'];
performanceReport(100)
performanceReport(1000)
performanceReport(100000)
//生成随机字符串
def genString(){
def s = '';
(1..4).each{
def index = random.nextInt(strings.size());
s += strings[index];
}
return s;
}
def performanceReport(int size) {
println("Report for list size $size")
def list = [];
(1..size).each {
list.add(genString())
}
def start = System.currentTimeMillis();
list.unique();
def end = System.currentTimeMillis();
def dur = end - start;
dur /= 1000;
println("unique use time $dur s");
list.clear();
(1..size).each {
list.add(genString())
}
start = System.currentTimeMillis();
def uSet = new HashSet(list.size());
for (e in list) {
uSet.add(e);
}
end = System.currentTimeMillis();
dur = end - start;
dur /= 1000;
println("hash set use time $dur s");
println('---------------------');
}
测试结果
重复运行代码三遍,发现结果对比的差值都比较固定,所以可以取其中一个结果作为参考。
Report for list size 100
unique use time 0.002 s
hash set use time 0.005 s
---------------------
Report for list size 1000
unique use time 0.049 s
hash set use time 0.001 s
---------------------
Report for list size 100000
unique use time 30.716 s
hash set use time 0.019 s
- 列表容量较少的时候(100左右),groovy的unique方法性能还是有一点点优势的
- 大容量的列表去重的时候,依旧比使用HashSet的速度慢了很多。所以如果大列表处理的时候,还是得选用HashSet。
Groovy unique方法源码分析
unique方法的源码如下:
public static <T> Collection<T> unique(Collection<T> self, boolean mutate) {
ArrayList answer = new ArrayList();
Iterator i$ = self.iterator();
//双重循环
while(i$.hasNext()) {
Object t = i$.next();
boolean duplicated = false;
Iterator i$1 = answer.iterator();
//循环传进来的带重复元素的列表而不是循环已经去重的列表
while(i$1.hasNext()) {
Object t2 = i$1.next();
if(coercedEquals(t, t2)) {
duplicated = true;
break;
}
}
if(!duplicated) {
answer.add(t);
}
}
if(mutate) {
self.clear();
self.addAll(answer);
}
return (Collection)(mutate?self:answer);
}
可以发现,非常不幸的两个导致性能底下的代码实现
- 双重循环
- 在判断重复元素的时候,是去循环原始的List,而不是新构建的容量较小的List,这个也会增加代码执行时间。
结论
一直以为这种框架性质的代码,一定是千锤百炼,写得优雅又高效的,不过事实就是:代码写得好看,性能并不一定好!
Groovy方法虽然便捷,但是使用内置的方法还是得谨慎,毕竟要处理超过100的列表是经常的事。