【问题标题】:LinkedList.contains execution speedLinkedList.contains 执行速度
【发布时间】:2011-02-17 18:08:03
【问题描述】:

为什么 Methode LinkedList.contains() 比这样的实现运行得快:

for (String s : list) 
   if (s.equals(element))
     return true;
return false;

我认为这与实现(我认为搜索对象不是空值)、相同的迭代器和等于操作之间没有太大区别

【问题讨论】:

  • 你测试过,发现它跑得更快了吗?还是你在假设? LinkedList.contains() 确实具有其他编码人员可以轻松理解您的意图的优势。

标签: java algorithm


【解决方案1】:

我决定对此进行测试并得出了一些有趣的结果

导入 java.util.LinkedList;

公共类包含{

private LinkedList<String> items = new LinkedList<String>();

public Contains(){
    this.addToList();
}

private void addToList(){
    for(int i=0; i<2000; i++){
        this.items.add("ItemNumber" + i);
    }
}

public boolean forEachLoop(String searchFor){
    for(String item : items){
        if(item.equals(searchFor))
            return true;
    }

    return false;
}

public boolean containsMethod(String searchFor){
    if(items.contains(searchFor))
        return true;

    return false;
}

}

和一个 JUnit 测试用例:

import static org.junit.Assert.assertEquals; import org.junit.Test; public class ContainsTest { @Test public void testForEachLoop(){ Contains c = new Contains(); boolean result = c.forEachLoop("ItemNumber1758"); assertEquals("Bug!!", true, result); } @Test public void testContainsMethod(){ Contains c = new Contains(); boolean result = c.containsMethod("ItemNumber1758"); assertEquals("Bug!!", true, result); } }

有趣的是,当我运行 JUnit 测试时,结果是: - testForEachLoop() - 0.014s - testContainsMethod() - 0.025s

这是真的还是我做错了什么?

【讨论】:

    【解决方案2】:

    让我们看看java.util.LinkedListthe source code(OpenJDK版本)

    public boolean contains(Object o) {
        return indexOf(o) != -1;
    }
    public int indexOf(Object o) {
        int index = 0;
        if (o==null) {
            /* snipped */ 
        } else {
            for (Entry e = header.next; e != header; e = e.next) {
                if (o.equals(e.element))
                    return index;
                index++;
            }
        }
        return -1;
    }
    

    如您所见,这是一个线性搜索,就像 for-each 解决方案一样,所以它不是渐近更快的。看看你的数字如何随着更长的列表增长会很有趣,但它可能是一个常数因素变慢。

    原因是 indexOf 在内部结构上工作,使用直接字段访问来迭代,而不是使用 Iterator&lt;E&gt; 的 for-each,其方法还必须额外检查诸如ConcurrentModificationException

    回到源头,你会发现LinkedListIterator&lt;E&gt;返回的E next()方法如下:

    private class ListItr implements ListIterator<E> {
       //...
       public E next() {
          checkForComodification();
          if (nextIndex == size)
          throw new NoSuchElementException();
    
          lastReturned = next;
          next = next.next;
          nextIndex++;
          return lastReturned.element;
      }
      final void checkForComodification() {
          if (modCount != expectedModCount)
             throw new ConcurrentModificationException();
      }
    

    这比LinkedList.contains 中的e = e.next; 更“繁忙”!一个LinkedListiterator()其实就是一个ListIterator,功能更丰富。在您的 for-each 循环中不需要它们,但不幸的是,无论如何您都必须为它们付费。更不用说必须执行所有对 ConcurrentModificationException 的防御性检查,即使在您迭代列表时不会对列表进行任何修改。


    结论

    所以是的,使用 for-each(或者更直接地说,使用它的 iterator()/listIterator())将 LinkedList 作为客户端迭代比 LinkedList 本身可以在内部执行的操作更昂贵。这是意料之中的,这就是为什么首先提供contains

    在内部工作给LinkedList 带来了巨大的优势,因为:

    • 它可以在防守检查中偷工减料,因为它知道自己没有违反任何不变量
    • 它可以走捷径并使用其内部表示

    那么你能从中学到什么? 熟悉 API! 看看已经提供了哪些功能;与您必须将它们复制为客户端相比,它们可能会更快。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多