为什么使用stream()调用比if语句耗费更多时间?

3

为什么要使用这个方法:

public static int howManyDifferentFields() {
    int difcolor = 0;
    difcolor++;
    if (fields[1] != fields[0]) {
        difcolor++;
    }
    if  (fields[2] != fields[1] && fields[2] != fields[0]) {
        difcolor++;
    }
    if (fields[3] != fields[2] && fields[3] != fields[1] && fields[3] != fields[0]) {
        difcolor++;
    }
    if (fields[4] != fields[3] && fields[4] != fields[2] && fields[4] != fields[1] && fields[4] != fields[0]) {
        difcolor++;
    }
    if (fields[5] != fields[4] && fields[5] != fields[3] && fields[5] != fields[2] && fields[5] != fields[1] && fields[5] != fields[0]) {
        difcolor++;
    }
    if (fields[6] != fields[5] && fields[6] != fields[4] && fields[6] != fields[3] && fields[6] != fields[2] && fields[6] != fields[1] && fields[6] != fields[0]) {
        difcolor++;
    }
    if (fields[7] != fields[6] && fields[7] != fields[5] && fields[7] != fields[4] && fields[7] != fields[3] && fields[7] != fields[2] && fields[7] != fields[1] && fields[7] != fields[0]) {
        difcolor++;
    }
    if (fields[8] != fields[7] && fields[8] != fields[6] && fields[8] != fields[5] && fields[8] != fields[4] && fields[8] != fields[3] && fields[8] != fields[2] && fields[8] != fields[1] && fields[8] != fields[0]) {
        difcolor++;
    }
    return difcolor;
}

比这快那么多?
public static int howManyDifferentFields2() {
    return (int) Arrays.stream(fields).distinct().count();
}

我希望使用第二种方式,因为代码量要少得多。但是这需要更多的时间!当我使用第二种方法而不是第一种方法时,程序需要大约8倍的时间才能完成。 我该怎么办?我能否以某种方式重写第一种方法,使其像第二种方法一样有效,但代码量更少? 在我看来,第二种方法看起来更好...

你如何测量时间?你认为第一个解决方案对于不同的数组大小是静态的,并且对于长数组大小是可行的吗? - Eklavya
我在程序的开头和结尾使用System.nanotime()来测量时间。我不太理解你的第二个问题 xD。 - Protagor
你尝试过其他方法,比如不使用流直接转换为集合,或者使用嵌套循环吗?(这不是对实际问题的答案,但可能是速度和可读性之间的妥协) - tobias_k
@tobias_k 我试过了,速度稍微快了一些,但仍然比第一种方法慢。 - Protagor
fields 是否总是一个包含9个元素的数组?元素类型是什么? - Ralf Kleberhoff
显示剩余3条评论
2个回答

4

大多数人认为流速很快。但实际上它们并不是这样。流背后有很多代码,而正是这些隐藏的开销在影响性能。

一般情况下,一个简单的for循环比非并行流(甚至在元素数量不大的情况下也比并行流)更快。

虽然流能够产生整洁和强大的代码,并且拥有许多好处,但对于小型流而言,性能不是其中之一。


您的逻辑具有O(n2)时间复杂度,但如果n固定为8,则可以将其重写为几乎等效的循环:

int difcolor = 8;
for (int i = 1; i < fields.length; i++) {
    for (int j = 0; j < i; j++) {
        if (fields[i] == fields[j]) {
            difcolor--;
            break;
       }
    }
}
return difcolor;

从完美分数开始,然后在第一次匹配中递减可以提高效率,因为它通过尽早退出内部循环来减少操作数量,类似于您使用的短路运算符 &&

您还可以使用集合(Set),它具有O(n)的时间复杂度,并且更紧凑,但是由于只有8个元素,它可能比上面的循环更慢:

Set<Integer> set = new HashSet<>(); // assuming fields is a int[] 
int difcolor = 0;
for (int field : fields) {
    if (set.add(field)) {
        difcolor++;
    }
}

这个方法与您的流式版本类似,但没有流式操作的开销。

如果元素已排序,则只需比较相邻元素即可在单次遍历中完成。


你会使用第一种方法吗?我认为它看起来很丑...你有更好的写法,但是效率一样吗? - Protagor
@Protagor 添加了一个更优雅的非流版本,性能可能相似。 - Bohemian

1
你可以采用以下解决方案:使用map来存储不同的值。此外,difcolor会有一个默认计数器,因此你可以从-1开始。
Map<Integer, Integer> myMap = new HashMap<Integer, Integer>();
        int difcolor = 0;
        
        for(int i=0;i <fields.length;i++) {
            if(!myMap.containsKey(fields[i])) {
                difcolor++;
                myMap.put(fields[i], i);
            }
        }
        System.out.println(difcolor);

3
如果你只需要读取键而非值,为什么要使用 Map 呢?使用 Set 就可以满足你的需求。 - Ralf Kleberhoff
HashSet是典型的Set实现,也基于哈希,其contains()方法也可以在几乎恒定的时间内工作,而无需迭代。 - Ralf Kleberhoff
哦!是的,我们可以使用 HashSet @RalfKleberhoff - Anirudh Jadhav

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接