Performance 使用Java流进行高效搜索

Performance 使用Java流进行高效搜索,performance,java-8,java-stream,Performance,Java 8,Java Stream,我有一个列表,其中的元素没有可测量的顺序。它们的属性也非常复杂,我不能简单地将它们插入到集合(因为不同的属性可能代表相同的元素) 在我的程序过程中,我分析了列表中的每个元素,并在此基础上添加了其他元素(比如构建一个图,然后到每个节点添加其他路径和节点)。但是,它们添加的元素可能与列表中的其他元素等效。在这种情况下,它们不会被添加,并且等效元素的属性发生了更改(假设是一个计数器) 我一直在使用此代码查找是否存在等效状态: public static State stateAlreadyExists

我有一个
列表
,其中的元素没有可测量的顺序。它们的属性也非常复杂,我不能简单地将它们插入到
集合
(因为不同的属性可能代表相同的元素)

在我的程序过程中,我分析了列表中的每个元素,并在此基础上添加了其他元素(比如构建一个图,然后到每个节点添加其他路径和节点)。但是,它们添加的元素可能与
列表中的其他元素等效。在这种情况下,它们不会被添加,并且等效元素的属性发生了更改(假设是一个计数器)

我一直在使用此代码查找是否存在等效状态:

public static State stateAlreadyExists(State current) {
    for (State any : list) {
        if (equivalencyMethod(any, current)) {
            return any;
        }
     }
     return null;
}
然而,这段代码虽然具有
O(n)
复杂性,但对于我的情况来说性能还不够。我添加的每个元素都将执行此代码,并且我为每个分析的元素添加了大约
sqrt(N)
元素(例如,分析元素400将创建20个新元素)。为了提高性能,我使用了Java的并行流:

public static State stateAlreadyExists(State current) {
    Optional<State> opt = list.parallelStream().filter(
        any -> equivalencyMethod(any, current)).findFirst();
    if (opt.isPresent()) {
        return opt.get();
    }
    return null;
}
公共静态stateAlreadyExists(当前状态){
可选opt=list.parallelStream().filter(
any->equivalencyMethod(any,current)).findFirst();
if(opt.isPresent()){
返回opt.get();
}
返回null;
}
性能显著提高。问题是这段代码实际上并不等价,因为我们在返回元素之前分析了整个流。大多数情况下,等价元素位于列表的第一个
sqrt(N)
元素中,因此在第一次匹配时停止的方法会更好

我知道有一个
noneFound()
方法用于。一旦找到匹配项,它就会返回。但是,它返回一个
布尔值
,而不是元素本身。是否有方法使用此方法或类似的构建方法返回找到的第一个匹配项

根据JavaDoc,findFirst():

返回此流的第一个元素

而findAny():

选择流中的任何元素。这是为了在并行操作中实现最大性能


因此,通过使用
findAny()
调用,我的代码可以变得更加高效,因为顺序对我的问题并不重要,因为在任何时候只有一个等效元素。

findFirst
是一种短路终端操作(谢谢)

此代码将被打印

创建A的次数:3

调用doStuff的次数:3

但不是

创建A的次数:20

调用doStuff的次数:20

甚至更少

创建A的次数:20

调用doStuff的次数:3


我建议你另一种方法。尝试以这样一种方式来制定等价性标准,即创建一个哈希函数,从而大大减少必须进行的成对等价性检查的数量。对于散列函数,如果两个不相等的项具有相同的散列,则没有问题,唯一重要的是两个相等的项具有相同的散列。然后,您只需将元素存储在
HashSet
中即可完成繁重的工作,您只需实现元素的
等于(Object other)
hashCode()


如果找不到合适的散列码,您仍然可以考虑是否能够对对象进行排序,也就是说,您可以制定一个比较函数,该函数可以判断任意两个对象的顺序(或者它们相等)。然后,您可以将
树集
与您的自定义比较器一起使用,这也会工作得非常快

findFirst()
在找到匹配项后立即返回(除非您的流已排序),因此除非必要,否则不会分析整个流。
findFirst()
不接受任何参数。它只返回流的第一个元素。我不理解你的最后一条评论。第一个元素不是您想要的吗?“因为我们在返回一个元素之前分析了整个流”-所以您的问题是,尽管您已经承认了性能提升,但凭空假设?我没有意识到
findFirst()
可能会中断过滤器操作,甚至是短路终端操作,这就是它在找到匹配项时停止的原因。是的,但它会搜索整个列表(全部20个元素),然后选择第一个匹配的元素。它不会在3点立即返回,是吗?是的,它会:doStuff只被调用了3次。如果它按照您所说的执行,则意味着findFirst不执行1循环,而是执行2循环。事实并非如此。@BlueMoon93我已经修改了我的代码,向您展示了1。仅生成3个
A
实例,2个。仅对
doStuff()
执行3次调用。因此,只有1个循环完成,代码不会创建20个
A
,然后只对它们进行过滤。@BlueMoon93:想想你是否真的需要第一个匹配,或者任何匹配都足够了。在后一种情况下,可以使用
findAny()
而不是
findFirst()
。这可以提高并行性能。我考虑了您的建议,但它们不适用于我的情况。没有可以使用的有意义的顺序,并且不能通过散列进行比较+不过我还是赞成这个想法
public static void main(String[] args) {
  final AtomicInteger countNew = new AtomicInteger();
  final AtomicInteger countDoStuff = new AtomicInteger();
  class A {
    A() { countNew.getAndIncrement(); }
    public boolean doStuff() { return countDoStuff.getAndIncrement() % 3 == 2; }
  }
  Stream.generate(A::new).limit(20).filter(A::doStuff).findFirst();
  System.out.println("Number of times an A was created: " + countNew);
  System.out.println("Number of times doStuff was called: " + countDoStuff);
}