经常查询的对象列表的最佳数据结构

时间:2010-05-07 00:47:48

标签: java data-structures

我有一个对象列表,如List。 Entity类有一个equals方法,在少数属性(业务规则)上区分一个Entity对象与另一个。

我们通常在此列表中执行的任务是删除所有重复的内容:

List<Entity> noDuplicates = new ArrayList<Entity>();
for(Entity entity: lstEntities)
{
    int indexOf = noDuplicates.indexOf(entity);
    if(indexOf >= 0 )
    {
            noDuplicates.get(indexOf).merge(entity);
    }
    else
    {
            noDuplicates.add(entity);
     }
}

现在,我一直在观察的问题是,一旦列表中的对象超过10000,代码的这一部分就会大大减慢。我知道arraylist正在进行o(N)搜索。

是否有更快的替代方案,使用HashMap不是一个选项,因为实体的唯一性是基于它的4个属性构建的,将密钥本身放入映射中会很繁琐吗?将更快的查询排序设置帮助?

由于

6 个答案:

答案 0 :(得分:3)

而不是列表结构,您可以使用一个集合(如果您关注实体唯一性,则更合适),正如Lars所建议的那样。此外,如果性能有问题,我会考虑使用TreeSet并实施Comparator来根据属性比较实体实例。树结构将允许快速(对数复杂度)插入,删除和检索操作。

答案 1 :(得分:2)

一个想法是使用Set而不是ListSet中没有重复项。要从列表中删除重复项,您只需将List添加到新的Set

即可
List<Entity> list = //your list.
Set<Entity> set = new HashSet<Entitiy>();
set.addAll(list);

但话说回来,也许有一些理由首先使用List?如果没有,您可以使用Set代替,而不必担心任何重复。

修改

Set中的元素没有索引引用(与List相比,您可以get(int index)执行此操作。 Set中的元素在没有特定参考点的情况下浮动。

如果您需要找到特定的一个,您需要遍历它们。如果这不合适和/或您不能没有索引引用 - 允许get(int index)remove(int index) - 我猜Set不适合您。

答案 2 :(得分:2)

  

现在,我一直在观察的问题是,只要列表的对象超过10000,代码的这一部分就会大大减慢。我理解arraylist正在进行o(N)搜索。

您发布的算法实际上比O(N)

更差
  • 遍历输入列表lstEntities - O(N)
  • 在此循环中,您正在调用必须扫描列表的ArrayList.indexOf(T) - 再次O(N)

您的算法实际上是O(N ^ 2),因为您可能在循环内扫描列表两次。

听起来你想要做的事实上是两个操作:

  1. 从输入List中删除所有重复项
  2. 当您找到重复项时,“合并”实体。
  3. 您可以通过仅扫描一次列表而不是嵌套循环来执行此操作。我建议您拆分Entity以将“标识”实体的字段移动到其他类型,例如ID,或者至少添加一个getID()方法,该方法可以返回这些字段分组为单一类型。这样,您可以轻松地在两种类型之间构建Map,以便能够合并具有“重复”标识的实体。这可能看起来像这样:

    Map<ID, Entity> map = new HashMap<ID, Entity>(inputList.size());
    for (Entity e : inputList) {
        Entity existing = map.get(e.getID());
        if (existing == null) {
            //not in map, add it
            map.put(e.getID(), e);
        } 
        else {
            existing.merge(e);
        }
    }
    

    遍历列表是O(n),而HashMap.get(K)是一个恒定时间操作。

答案 3 :(得分:1)

这一切都取决于merge操作正在做什么。执行merge时,equals是否更改了所比较的任何属性?如果没有,那么你会惊讶于如果这样做会更快:

首先,为hashCode类定义Entity,与equals的定义兼容。一种常见的方法是:

public int hashCode() {
  // assuming the four attributes that determine equality are called
  // attrFoo, attrBar, attrBaz, and attrQux
  int hash = 1;
  hash += attrFoo == null ? 0 : attrFoo.hashCode();
  hash *= 37;
  hash += attrBar == null ? 0 : attrBar.hashCode();
  hash *= 37;
  hash += attrBaz == null ? 0 : attrBaz.hashCode();
  hash *= 37;
  hash += attrQux == null ? 0 : attrQux.hashCode();

  return hash;
}

然后,使用HashMap以便您可以找到以下内容:

Map<Entity, Entity> map = new HashMap<Entity, Entity>();
for(Entity entity: lstEntities) {
  if (map.containsKey(entity)) {
    map.get(entity).merge(entity);
  } else {
    map.put(entity, entity);
  }
}
return map.values();  // or keys().  Whichever.

我应该注意到,编写上面的代码时我感觉有点脏,因为你真的不应该创建不是一成不变的Map个键,但是这样做会比你的更快,更快。现在就做。

答案 4 :(得分:0)

除非你有理由需要订购List,否则你可能最好使用Set - 特别是HashSet。

我看到你对使用散列集合的担忧,因为“实体的唯一性是建立在它的4个属性上”,但这很容易克服。你只需要定义一个与你现有的equals()方法兼容的hashcode()方法,然后你可以将你的实体插入一个Set中,作为一个神奇的副作用,永远不必再删除重复。

答案 5 :(得分:0)

O(N * Log(N))算法的两个简单步骤:

  1. 使用基于四个重要字段的比较器对列表进行排序
  2. 遍历列表,将每个项目与列表中的下一个项目进行比较,如果它们相等,则将它们合并并删除一个项目。