如何最好地比较Java中的两个集合并采取行动?

我有两个相同的对象集合, Collection<Foo> oldSetCollection<Foo> newSet 。 所需的逻辑如下:

  • 如果foo在(*) oldSet但不是newSet ,则调用doRemove(foo)
  • 否则如果foo不在oldSet而是在newSet ,则调用doAdd(foo)
  • 否则,如果foo在两个集合中都被修改,则调用doUpdate(oldFoo, newFoo)
  • else if !foo.activated && foo.startDate >= now ,调用doStart(foo)
  • else if foo.activated && foo.endDate <= now ,call doEnd(foo)

(*)“in”表示唯一标识符匹配,不一定是内容。

当前(遗留)代码会执行许多比较来计算removeSetaddSetupdateSetstartSetendSet ,然后循环以对每个项目执行操作。

代码非常混乱(部分原因是我已经遗漏了一些意大利面条的逻辑),我试图重构它。 一些更多的背景信息:

  • 据我所知, oldSetnewSet实际上是由ArrayList支持的
  • 每套包含less于100个项目,最有可能超过20
  • 这个代码经常被调用(以百万/天衡量),尽pipe这些集合很less有不同

我的问题:

  • 如果我把oldSetnewSet转换成HashMap<Foo> (顺序不在这里),用ID作为关键字,它会使代码更容易阅读和更容易比较吗? 转换的时间和内存性能损失多less?
  • 迭代这两个集合,并执行适当的操作更高效和简洁?

Apache的commons.collections库有一个CollectionUtils类,为集合操作/检查提供了易于使用的方法,例如交集,差异和联合。

org.apache.commons.collections.CollectionUtils API文档在这里 。

例如,您可以使用Java 8stream

 set1.stream().filter(s -> set2.contains(s)).collect(Collectors.toSet()); 

或设置类从番石榴 :

 Set<String> intersection = Sets.intersection(set1, set2); Set<String> difference = Sets.difference(set1, set2); Set<String> symmetricDifference = Sets.symmetricDifference(set1, set2); Set<String> union = Sets.union(set1, set2); 

我已经创build了一个近似值,我认为你只是在Java中使用Collections Framework。 坦率地说,我认为这可能是矫枉过正的@Mike甲板指出。 对于比较和处理这样一小组项目,我认为从程序angular度来看,数组将是一个更好的select,但这里是我的伪代码(因为我很懒)的解决scheme。 我有一个假设,Foo类是基于它的唯一的id而不是它的内容中的所有数据:

 Collection<Foo> oldSet = ...; Collection<Foo> newSet = ...; private Collection difference(Collection a, Collection b) { Collection result = a.clone(); result.removeAll(b) return result; } private Collection intersection(Collection a, Collection b) { Collection result = a.clone(); result.retainAll(b) return result; } public doWork() { // if foo is in(*) oldSet but not newSet, call doRemove(foo) Collection removed = difference(oldSet, newSet); if (!removed.isEmpty()) { loop removed { Foo foo = removedIter.next(); doRemove(foo); } } //else if foo is not in oldSet but in newSet, call doAdd(foo) Collection added = difference(newSet, oldSet); if (!added.isEmpty()) { loop added { Foo foo = addedIter.next(); doAdd(foo); } } // else if foo is in both collections but modified, call doUpdate(oldFoo, newFoo) Collection matched = intersection(oldSet, newSet); Comparator comp = new Comparator() { int compare(Object o1, Object o2) { Foo f1, f2; if (o1 instanceof Foo) f1 = (Foo)o1; if (o2 instanceof Foo) f2 = (Foo)o2; return f1.activated == f2.activated ? f1.startdate.compareTo(f2.startdate) == 0 ? ... : f1.startdate.compareTo(f2.startdate) : f1.activated ? 1 : 0; } boolean equals(Object o) { // equal to this Comparator..not used } } loop matched { Foo foo = matchedIter.next(); Foo oldFoo = oldSet.get(foo); Foo newFoo = newSet.get(foo); if (comp.compareTo(oldFoo, newFoo ) != 0) { doUpdate(oldFoo, newFoo); } else { //else if !foo.activated && foo.startDate >= now, call doStart(foo) if (!foo.activated && foo.startDate >= now) doStart(foo); // else if foo.activated && foo.endDate <= now, call doEnd(foo) if (foo.activated && foo.endDate <= now) doEnd(foo); } } } 

至于你的问题:如果我将oldSet和newSet转换成HashMap(这里的顺序不是关注的),用ID作为键,它会使代码更容易阅读和更容易比较? 转换的时间和内存性能损失多less? 我认为你可能会通过使用Map BUT使代码更具可读性……在转换过程中,您可能会使用更多的内存和时间。

迭代这两个集合,并执行适当的操作更高效和简洁? 是的,这将是两全其美的,特别是如果你遵循@Mike Sharek的build议,用专门的方法来滚动你自己的列表,或者按照访问者devise模式来运行你的收集和处理每个项目。

我会转到列表并解决这个问题:

  1. 如果列表中的对象不是Comparable,则使用自定义比较器按id升序sorting这两个列表
  2. 迭代两个列表中的元素(如合并sortingalgorithm中的合并阶段),但不是合并列表,而是检查逻辑。

代码将或多或less像这样:

 /* Main method */ private void execute(Collection<Foo> oldSet, Collection<Foo> newSet) { List<Foo> oldList = asSortedList(oldSet); List<Foo> newList = asSortedList(newSet); int oldIndex = 0; int newIndex = 0; // Iterate over both collections but not always in the same pace while( oldIndex < oldList.size() && newIndex < newIndex.size()) { Foo oldObject = oldList.get(oldIndex); Foo newObject = newList.get(newIndex); // Your logic here if(oldObject.getId() < newObject.getId()) { doRemove(oldObject); oldIndex++; } else if( oldObject.getId() > newObject.getId() ) { doAdd(newObject); newIndex++; } else if( oldObject.getId() == newObject.getId() && isModified(oldObject, newObject) ) { doUpdate(oldObject, newObject); oldIndex++; newIndex++; } else { ... } }// while // Check if there are any objects left in *oldList* or *newList* for(; oldIndex < oldList.size(); oldIndex++ ) { doRemove( oldList.get(oldIndex) ); }// for( oldIndex ) for(; newIndex < newList.size(); newIndex++ ) { doAdd( newList.get(newIndex) ); }// for( newIndex ) }// execute( oldSet, newSet ) /** Create sorted list from collection If you actually perform any actions on input collections than you should always return new instance of list to keep algorithm simple. */ private List<Foo> asSortedList(Collection<Foo> data) { List<Foo> resultList; if(data instanceof List) { resultList = (List<Foo>)data; } else { resultList = new ArrayList<Foo>(data); } Collections.sort(resultList) return resultList; } 

我认为最简单的方法是通过使用apache集合API – CollectionUtils.subtract(list1,list2),只要列表是相同的types。

 public static boolean doCollectionsContainSameElements( Collection<Integer> c1, Collection<Integer> c2){ if (c1 == null || c2 == null) { return false; } else if (c1.size() != c2.size()) { return false; } else { return c1.containsAll(c2) && c2.containsAll(c1); } } 

对于一个小的一般是不值得从一个数组转换成一个HashMap /设置。 事实上,你可能最好把它们放在一个数组中,然后用键对它们进行sorting,并同时迭代这两个列表来做比较。

为了共同映射一个列表或集合,我们可以使用Arrays.equals(object[], object[]) 。 它只会检查值。 为了得到Object[]我们可以使用Collection.toArray()方法。