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

Most efficient way to see if an ArrayList contains an object in Java

我在Java中有一个对象列表。对象有四个字段,其中两个字段用于将对象视为等于另一个字段。考虑到这两个字段,我正在寻找最有效的方法来查看数组是否包含该对象。

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

有没有更好的方法,而不仅仅是循环遍历和手动比较每个对象的两个字段,然后在找到时中断?这看起来太乱了,正在寻找更好的方法。

编辑:arraylist来自一个SOAP响应,该响应被解编为对象。


这取决于你需要的东西有多高效。简单地遍历列表,查找满足特定条件的元素是o(n),但arraylist也是。如果可以实现equals方法,则包含。如果您不在循环或内部循环中执行此操作,则此方法可能很好。

如果你真的需要非常有效的查找速度,不惜一切代价,你需要做两件事:

  • 围绕着这样一个事实工作生成:编写一个适配器类,无法包装生成的类并它基于equals()实现在这两个领域(假设是公共的)。别忘了还有实现hashcode()(*)
  • 用适配器包装每个对象,然后把它放在哈希集中。hashset.contains()具有常量访问时间,即O(1)而不是O(N)。
  • 当然,构建这个哈希集仍然需要O(N)成本。只有当构建哈希集的成本与您需要执行的所有contains()检查的总成本相比微不足道时,您才能获得任何东西。尝试构建一个没有重复项的列表就是这样。

    *()实现hashcode()最好是由xor'ing(^ operator)对您用于equals实现的相同字段的hashcode执行(但乘以31可减少xor产生0的机会)。


    您可以使用比较器与Java的内置方法进行排序和二进制搜索。假设您有一个这样的类,其中A和B是要用于排序的字段:

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

    您将定义比较器:

    1
    2
    3
    4
    5
    6
    7
    8
    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);
      }
    };

    然后对列表进行排序:

    1
    Collections.sort(list, comparator);

    最后进行二进制搜索:

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


    考虑到您的限制,您将陷入蛮力搜索(或创建一个索引,如果搜索将重复)。你能详细描述一下ArrayList是如何产生的吗?也许那里有一些回旋的空间。

    如果您要查找的只是更漂亮的代码,那么可以考虑使用Apache Commons Collections类,特别是CollectionUtils.find(),来获得现成的语法糖分:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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);
       }
    });


    如果列表已排序,则可以使用二进制搜索。如果没有,那就没有更好的办法了。

    如果你经常这样做,那么第一次对列表进行排序几乎肯定是值得的。由于不能修改类,因此必须使用Comparator来进行排序和搜索。


    如果您是我的foreach DSL的用户,可以使用Detect查询来完成。

    1
    2
    3
    4
    5
    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();

    即使equals方法正在比较这两个字段,那么从逻辑上讲,它将与手工操作的代码相同。好吧,可能是"凌乱",但这仍然是正确的答案


    也许清单不是你需要的。

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

    Linkedhashmap可能更适合您的用例,也请检查一下。


    Is there any better way than just looping through and manually comparing the two fields for each object and then breaking when found? That just seems so messy, looking for a better way.

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

    如果您真正关心效率,那么可以创建一个自定义列表实现,该实现使用对象中的字段作为散列,并使用散列映射作为存储。但这可能太多了。

    然后,您必须将填充数据的位置从arraylist更改为rcustomlist。

    像:

    1
    2
    3
     List list = new ArrayList();

     fillFromSoap( list );

    到:

    1
    2
    3
     List list = new MyCustomSpecialList();

     fillFromSoap( list );

    实现方式如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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() );
        }

    }

    与散列集非常相似,这里的问题是散列集依赖于散列代码方法的良好实现,这可能是您没有的。相反,您使用"您知道的字段"作为散列值,它使一个对象与另一个对象相等。

    当然,从头到尾实现一个列表要比上面的代码片段复杂得多,这就是为什么我说Fabian Steeg建议会更好更容易实现的原因(尽管像这样的东西更有效)。

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


    有三个基本选项:

    1)如果检索性能是最重要的,并且这样做是可行的,那么使用一次构建的散列表的形式(如果列表发生更改,则进行更改)。

    2)如果列表排序方便或排序可行,并且O(log n)检索足够,则进行排序和搜索。

    3)如果O(N)检索足够快,或者如果操作/维护数据结构或替代数据结构不切实际,则对列表进行迭代。

    在编写比简单的列表迭代更复杂的代码之前,需要考虑一些问题。

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

    • 您对所讨论的数据结构有多大的控制权?你能影响它的建造方式吗?以后管理?

    • 数据结构(和底层对象)的生命周期是什么?它是一次性建立的,从未改变,还是高度动态的?你的代码能监控(甚至改变)它的生命周期吗?

    • 是否还有其他重要的约束,如内存占用?有关复制品的信息重要吗?等。


    如果你需要在同一个列表中搜索很多次,那么建立一个索引可能是值得的。

    迭代一次,并使用您要查找的等量值作为键,并使用适当的节点作为值构建哈希图。如果需要all而不是给定的equals值中的任何一个,那么让map具有值类型list,并在初始迭代中构建整个列表。

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


    从性能的角度来看,基于字段值构建这些对象的哈希图是值得的,例如只填充一次映射并非常有效地查找对象


    我认为最简单的解决方案是包装对象并将contains调用委托给包装类的集合。这类似于Comparator,但并不强制您对结果集合进行排序,您只需使用arraylist.contains()。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    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();
            }

        }