Contents
  1. 1. 数字列表的去重
    1. 1.1. 测试代码
    2. 1.2. 测试结果
  2. 2. 字符串列表的去重
    1. 2.1. 测试代码
    2. 2.2. 测试结果
  3. 3. Groovy unique方法源码分析
  4. 4. 结论

最近系统遇到一个问题就是一个调用一个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

我重复运行了代码三遍,出来的结果都差不多,所以对比结果基本是确定的:

  1. List比较少元素的情况下,比如100以内,运行效率差不了多少
  2. 当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
  1. 列表容量较少的时候(100左右),groovy的unique方法性能还是有一点点优势的
  2. 大容量的列表去重的时候,依旧比使用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);
}

可以发现,非常不幸的两个导致性能底下的代码实现

  1. 双重循环
  2. 在判断重复元素的时候,是去循环原始的List,而不是新构建的容量较小的List,这个也会增加代码执行时间。

结论

一直以为这种框架性质的代码,一定是千锤百炼,写得优雅又高效的,不过事实就是:代码写得好看,性能并不一定好!
Groovy方法虽然便捷,但是使用内置的方法还是得谨慎,毕竟要处理超过100的列表是经常的事。

Contents
  1. 1. 数字列表的去重
    1. 1.1. 测试代码
    2. 1.2. 测试结果
  2. 2. 字符串列表的去重
    1. 2.1. 测试代码
    2. 2.2. 测试结果
  3. 3. Groovy unique方法源码分析
  4. 4. 结论