【问题标题】:In Java what is the quickest way to check if list contains items from another list, both list are of same type?在Java中检查列表是否包含另一个列表中的项目的最快方法是什么,两个列表都属于同一类型?
【发布时间】:2015-10-29 00:54:13
【问题描述】:

假设我有一个名为 MyClass 的类,如下所示:

public class MyClass
{
     //Identifier is alpha-numeric. If the identifier starts will 'ZZ'
     //is special special identifier.
     private String identifier = null;
     //Date string format YYYY-MM-DD
     private String dateString = null;
     //Just a flag (not important for this scenario)
     private boolean isCoolCat = false;
     //Default Constructor and getters/setters implemented
     //Overrides the standard Java equals() method.
     //This way, when ArrayList calls contains() for MyClass objects
     //it will only check the Date (for ZZ identifier) 
     //and identifier values against each other instead of
     //also comparing the isCoolCat indicator value.
     @Override
     public boolean equals(Object obj)
     {
          if(this == obj)
          {
               return true;
          }
          if(obj == null)
          {
               return false;
          }
          if(getClass() != obj.getClass())
          {
               return false;
          }
          MyClass other = (MyClass) obj;
          if(this.identifier == null)
          {
               if(other.identifier != null)
               {
                    return false;
               }
          } else if(!this.identifier.equals(other.identifier)) {
               return false;
          }
          if(other.identifier.startsWith("ZZ"))
          {
               if(!this.dateString.equals(other.dateString))
               {
                    return false;
               }
          }
          return true;
     }
}

在另一个类中,我有两个 MyClass 类型的 List,每个包含 100,000 个对象。我需要检查一个列表中的项目是否在另一个列表中,我目前完成此操作如下:

`

List<MyClass> inList = new ArrayList<MyClass>();
List<MyClass> outList = new ArrayList<MyClass>();
inList = someMethodForIn();
outList = someMethodForOut();
//For loop iterates through inList and check if outList contains
//MyClass object from inList if it doesn't then it adds it.
for(MyClass inObj : inList)
{
     if(!outList.contains(inObj))
     {
          outList.add(inObj); 
     }
}

我的问题是:这是完成此任务的最快方法吗?如果不能,您能否向我展示一个更好的实现,它将给我带来性能提升?列表大小并不总是 100,000。目前在我的平台上,100,000 大小大约需要 2 分钟。假设它可以在 1 到 1,000,000 之间变化。

【问题讨论】:

  • List#retainAll(Collection),应该返回列表之间所有相同元素的List。最快的,也许不是,最简单的,可能是(别忘了,你要先复制原始的List ;))
  • @MadProgrammer 我也不确定这是否会更快。还需要更多内存,因为现在我必须复制可以包含 1,000,000 个对象的列表。此外,retainAll 将返回两个列表之间所有相同元素的列表。但这将如何帮助我确定哪些对象 inList 不在 outList 中,以及如何将它们添加到 outList 中?我认为这种方法不会更快。
  • 好吧,如果您使用LinkedList,内存将是一个问题,因为您只维护对对象的引用,而不是新副本;)。 inList.retainAll(outList) 将返回一个List,其中包含来自inList 的所有对象,这些对象在outList 中,使用outList.retainAll(inList) 进行反向操作。当然,您也可以使用removeAll,这将在不匹配的实体上留下List ;)
  • @MadProgrammer 从设计的角度来看,这是我最喜欢的答案。在代码中,这将经过很好的测试和理解。

标签: java list optimization


【解决方案1】:

您想为此使用SetSet 有一个 contains 方法,可以在 O(1) 时间内确定对象是否在集合中。

List&lt;MyClass&gt; 转换为Set&lt;MyClass&gt; 时需要注意的几点:

  1. 您将失去元素的顺序
  2. 您将丢失重复的元素
  3. 您的MyClass 需要实现hashcode()equals()they should be consistent

要将您的List 转换为Set,您可以使用:

Set<MyObject> s1 = new HashSet<>(inList);
Set<MyObject> s2 = new HashSet<>(outList);

这个Java doc 解释了如何找到两个集合的并集、交集和差集。特别是,您似乎对联盟感兴趣:

// transforms s2 into the union of s1 and s2. (The union of two sets 
// is the set containing all of the elements contained in either set.)
s2.addAll(s1)

【讨论】:

  • 谢谢!我不是在寻找十字路口。我需要检查 inList 中的 MyClass 对象是否存在于 outList 中(忽略 isCoolCat 变量的值),如果不存在,则需要将该对象添加到 outList。您的 1 和 2 对我没有影响,因为订购并不重要,可以删除重复项。我想知道我是否合并这两个列表,然后将其转换为 Set ,然后再返回列表,如果那是最快的。如果是这种情况,当转换为 Set 时,它会自动删除重复项。
  • 但是,如果所有变量都相同,除了 isCoolCat 指示符在一个为真而另一个为假之外,这是否会被我的覆盖 equals 方法视为重复?
  • 您可以随意定义hashcodeequals 方法。如果您想忽略 isCoolCat 变量,请不要将其包含在 equals 实现中。
  • 顺便说一句,Set 也有一个 Union 方法,从您的描述来看,您似乎正在这样做。我会更新答案。另外,这应该是最快的方式,O(n),时间。
  • 谢谢@bcorso。您的解决方案效果最好。我注意到列表中 MyClass 的数量很少,这两种解决方案似乎都需要大约相同的时间。我实现如下:'Set tempOutSet = Collections.synchronizedSet(new HashSet(inList.size()+1,1)); tempOutSet.addAll(outList); tempOutSet.addAll(inList); outList = new ArrayList(tempOutSet);'在我的平台上,对于 size = 100,000,原始实现耗时 1 分钟 55 秒 268 毫秒,根据您的建议进行的实现耗时 39 秒 956 毫秒。跨度>
【解决方案2】:

2 分钟比较 2 个非常大的列表,在这里可能不会节省太多时间,所以根据您的应用程序,您是否可以设置一个标志,以便依赖于此的东西在完成之前无法运行并将其推送到它自己的线程中,并且让用户做其他事情(同时也告诉他们这是在进行中。)或者至少设置一个进度条。让用户知道应用程序很忙,并告诉他们(ish)在像这样的非常复杂的计算中只需要几分钟的时间就可以了,并且可能比仅仅节省几秒钟的时间更好。如果用户知道他们会等多久,并且你告诉他们有时间去喝杯咖啡,他们会非常容忍延迟。

【讨论】:

  • 不适用于我目前的情况,但感谢您的建议,并将记住以备将来使用。
【解决方案3】:

散列!哈希始终是答案!

此代码的当前复杂度为O(nm),其中ninList 的大小,moutList 的大小。

您可以使用HashSet 将复杂性降低到O(n)。因为contains 现在会占用O(1)

可以这样做,

   HashSet<MyClass> outSet = new HashSet<>(outList);
   for(MyClass inObj : inList)
   {
        if(!outSet.contains(inObj))
        {
              outList.add(inObj); 
         }
    }

来源和来源。

returning difference between two lists in java

Time complexity of contains(Object o), in an ArrayList of Objects

HashSet.contains performance

【讨论】:

  • 感谢您的建议,但 bcorso 的解决方案(与您的类似)效果最好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-13
  • 1970-01-01
  • 1970-01-01
  • 2012-08-01
  • 1970-01-01
相关资源
最近更新 更多