查看ArrayList是否包含Java中的对象的最有效方法

时间:2009-02-17 22:18:58

标签: java algorithm optimization search arraylist

我有一个Java对象的ArrayList。这些对象有四个字段,其中两个我用来将对象视为另一个。我正在寻找最有效的方法,给定这两个字段,看看数组是否包含该对象。

扳手是这些类是基于XSD对象生成的,所以我不能修改类本身来覆盖.equals

有没有更好的方法,只需循环并手动比较每个对象的两个字段,然后在找到时断开?这看起来很混乱,寻找更好的方式。

编辑: ArrayList来自一个解组到对象中的SOAP响应。

12 个答案:

答案 0 :(得分:101)

这取决于你需要的东西的效率。简单地遍历列表寻找满足某个条件的元素是O(n),但是如果你可以实现Equals方法,那么ArrayList.Contains也是如此。如果你不是在循环或内循环中这样做,这种方法可能就好了。

如果你真的需要不惜一切代价提高效率,你需要做两件事:

  1. 解决课堂这个事实 生成:写一个适配器类 可以包装生成的类和 实现基于equals()的 在这两个领域(假设他们 是公开的)。别忘了 实施hashCode()(*)
  2. 用该适配器包裹每个对象 把它放在一个HashSet中。 HashSet.contains()不变 访问时间,即O(1)而不是O(n)。
  3. 当然,构建此HashSet仍然会产生O(n)成本。如果构建HashSet的成本与您需要执行的所有contains()检查的总成本相比可以忽略不计,那么您将获得任何收益。试图建立一个没有重复的列表就是这种情况。


    * ()实现hashCode()最好通过XOR'ing(^运算符)用于equals实现的相同字段的hashCodes(但是multiply by 31来减少XOR产生0的机会)

答案 1 :(得分:37)

您可以使用带有Java内置方法的Comparator进行排序和二分查找。假设您有一个这样的类,其中a和b是您要用于排序的字段:

class Thing { String a, b, c, d; }

您可以定义比较器:

Comparator<Thing> comparator = new Comparator<Thing>() {
  public int compare(Thing o1, Thing o2) {
    if (o1.a.equals(o2.a)) {
      return o1.b.compareTo(o2.b);
    }
    return o1.a.compareTo(o2.a);
  }
};

然后对列表进行排序:

Collections.sort(list, comparator);

最后进行二元搜索:

int i = Collections.binarySearch(list, thingToFind, comparator);

答案 2 :(得分:10)

考虑到你的限制,你会遇到暴力搜索(或者如果重复搜索就会创建索引)。你能详细说明ArrayList是如何产生的 - 也许那里有一些摆动的空间。

如果您所寻找的只是更漂亮的代码,请考虑使用Apache Commons Collections类,特别是CollectionUtils.find(),用于现成的语法糖:

ArrayList haystack = // ...
final Object needleField1 = // ...
final Object needleField2 = // ...

Object found = CollectionUtils.find(haystack, new Predicate() {
   public boolean evaluate(Object input) {
      return needleField1.equals(input.field1) && 
             needleField2.equals(input.field2);
   }
});

答案 3 :(得分:6)

如果列表为sorted,您可以使用binary search。如果没有,那就没有更好的方法了。

如果你这么做很多,那么第一次对列表进行排序几乎肯定是值得的。由于您无法修改类,因此您必须使用Comparator进行排序和搜索。

答案 4 :(得分:4)

即使equals方法 比较这两个字段,那么从逻辑上讲,它与您手动执行的代码完全相同。好吧,它可能是“凌乱”,但它仍然是正确的答案

答案 5 :(得分:4)

如果您是ForEach DSL的用户,则可以使用Detect查询完成此操作。

Foo foo = ...
Detect<Foo> query = Detect.from(list);
for (Detect<Foo> each: query) 
    each.yield = each.element.a == foo.a && each.element.b == foo.b;
return query.result();

答案 6 :(得分:2)

  

有没有更好的方法,只需循环并手动比较每个对象的两个字段,然后在找到时断开?这看起来很混乱,寻找更好的方法。

如果您的关注点是可维护性的,那么您可以执行Fabian Steeg建议(这就是我要做的),尽管它可能不是“最有效”(因为您必须先对数组进行排序然后执行二进制文件)搜索)但肯定是最干净,更好的选择。

如果您真的关心效率,可以创建一个自定义List实现,该实现使用对象中的字段作为哈希,并使用HashMap作为存储。但可能这太过分了。

然后,您必须更改将数据从ArrayList填充到YourCustomList的位置。

喜欢:

 List list = new ArrayList();

 fillFromSoap( list );

致:

 List list = new MyCustomSpecialList();

 fillFromSoap( list );

实施将如下所示:

class MyCustomSpecialList extends AbstractList  { 
    private Map<Integer, YourObject> internalMap;

    public boolean add( YourObject o ) { 
         internalMap.put( o.getThatFieldYouKnow(), o );
    }

    public boolean contains( YourObject o ) { 
        return internalMap.containsKey( o.getThatFieldYouKnow() );
    }

}

非常像HashSet,这里的问题是HashSet依赖于hashCode方法的良好实现,这可能是你没有的。相反,你使用散列“你知道的那个字段”,这是使一个对象等于另一个对象的散列。

当然从头开始实现一个List比上面的代码片段更棘手,这就是为什么我说Fabian Steeg建议会更好更容易实现(尽管这样的事情会更有效)

告诉我们你最后做了什么。

答案 7 :(得分:2)

也许列表不是你需要的。

也许TreeSet会是一个更好的容器。你得到O(log N)插入和检索,以及有序迭代(但不允许重复)。

LinkedHashMap对您的用例可能更好,也可以检查一下。

答案 8 :(得分:1)

从性能角度来看,基于字段值作为键构建这些对象的HashMap是值得的,例如:填充地图一次并非常有效地查找对象

答案 9 :(得分:1)

如果您需要在同一个列表中搜索很多时间,那么建立索引可能会有所收获。

迭代一遍,构建一个HashMap,其中包含您要查找的等于的值以及作为值的相应节点。如果您需要all而不是给定equals值中的任何一个,那么让map具有值类型的list并在初始迭代中构建整个列表。

请注意,在执行此操作之前,您应该进行测量,因为构建索引的开销可能会超出遍历,直到找到预期的节点。

答案 10 :(得分:1)

有三个基本选项:

1)如果检索性能至关重要且实际可行,请使用一次构建的哈希表(如果列表更改,则更改为哈希表)。

2)如果方便地对List进行排序或者对它进行排序是可行的,并且O(log n)检索就足够了,可以进行排序和搜索。

3)如果O(n)检索足够快或者操作/维护数据结构或替换是不切实际的,则迭代List。

在编写比List上的简单迭代更复杂的代码之前,值得思考一些问题。

  • 为什么需要不同的东西? (时间)表现?优雅?可维护性?重用?所有这些都是好的理由,除了或者在一起,但它们会影响解决方案。

  • 您对相关数据结构有多少控制权?你能影响它的构建方式吗?稍后管理?

  • 数据结构(和底层对象)的生命周期是什么?它是一次性构建的,永远不会改变,还是高度动态的?您的代码可以监控(甚至改变)其生命周期吗?

  • 是否存在其他重要限制因素,例如内存占用?有关重复的信息是否重要?等

答案 11 :(得分:0)

我想说最简单的解决方案是包装对象并将contains调用委托给包装类的集合。这与比较器类似,但不会强制您对生成的集合进行排序,您只需使用ArrayList.contains()。

public class Widget {
        private String name;
        private String desc;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }
    }



    public abstract class EqualsHashcodeEnforcer<T> {

        protected T wrapped;

        public T getWrappedObject() {
            return wrapped;
        }

        @Override
        public boolean equals(Object obj) {
            return equalsDelegate(obj);
        }

        @Override
        public int hashCode() {
            return hashCodeDelegate();
        }

        protected abstract boolean equalsDelegate(Object obj);

        protected abstract int hashCodeDelegate();
    }


    public class WrappedWidget extends EqualsHashcodeEnforcer<Widget> {

        @Override
        protected boolean equalsDelegate(Object obj) {
            if (obj == null) {
                return false;
            }
            if (obj == getWrappedObject()) {
                return true;
            }
            if (obj.getClass() != getWrappedObject().getClass()) {
                return false;
            }
            Widget rhs = (Widget) obj;

            return new EqualsBuilder().append(getWrappedObject().getName(),
                    rhs.getName()).append(getWrappedObject().getDesc(),
                    rhs.getDesc()).isEquals();
        }

        @Override
        protected int hashCodeDelegate() {

            return new HashCodeBuilder(121, 991).append(
                    getWrappedObject().getName()).append(
                    getWrappedObject().getDesc()).toHashCode();
        }

    }