繁体   English   中英

为什么 list.size()>0 在 Java 中比 list.isEmpty() 慢?

[英]Why is list.size()>0 slower than list.isEmpty() in Java?

为什么list.size()>0在 Java 中比list.isEmpty()慢? 换句话说,为什么isEmpty()size()>0更可取?

当我查看ArrayList中的实现时,看起来速度应该是一样的:

ArrayList.size()

    /**
     * Returns the number of elements in this list.
     *
     * @return the number of elements in this list
     */
    public int size() {
      return size;
    }

ArrayList.isEmpty()

    /**
     * Returns <tt>true</tt> if this list contains no elements.
     *
     * @return <tt>true</tt> if this list contains no elements
     */
    public boolean isEmpty() {
        return size == 0;
     }

如果我们只是编写一个简单的程序来获取这两种方法所花费的时间,那么 case size() isEmpty() ,为什么会这样呢?

这是我的测试代码;

import java.util.List;
import java.util.Vector;

public class Main {
    public static void main(String[] args) {
        List l=new Vector();
        int i=0;
        for(i=0;i<10000;i++){
            l.add(new Integer(i).toString());
        }
        System.out.println(i);
        Long sTime=System.nanoTime();
        l.size();
        Long eTime=System.nanoTime();
        l.isEmpty();
        Long eeTime=System.nanoTime();
        System.out.println(eTime-sTime);
        System.out.println(eeTime-eTime);
    }
}

这里eTime-sTime>eeTime-eTime在所有情况下。 为什么?

对于ArrayList ,是的——你是正确的,操作需要(大致)相同的时间。

对于List的其他实现——例如,一个简单的链表*——计算大小可能需要很长时间,而您实际上只关心它是否大于零。

因此,如果您绝对知道列表是ArrayList的实现并且永远不会改变,那么这并不重要; 但:

  1. 这是将自己束缚在特定实现上的不好的编程习惯。
  2. 如果几年后随着代码重组而发生变化,测试将表明“它有效”,但运行效率比以前低了。
  3. 即使在最好的情况下, size() == 0仍然不比isEmpty(),因此没有令人信服的理由使用前者。
  4. isEmpty()更清晰地定义了您真正关心和正在测试的内容,因此使您的代码更容易理解。

* 我最初在这里编写 LinkedList ,隐式引用java.util.LinkedList ,尽管该特定实现确实显式存储了它的大小,使size()在这里成为 O(1) 操作。 一个简单的链表操作可能不会这样做,并且在更一般的意义上, List的实现没有效率保证。

您的测试代码有缺陷。

只需颠倒顺序,即首先调用 isEmpty 和 size > 0 秒,您将得到相反的结果。 这是由于类加载、缓存等造成的。

对不起,你的基准有缺陷。 看一下Java 理论和实践:有缺陷的微基准的剖析,以获得有关如何处理基准的一般描述。


更新:为了获得适当的基准,您应该查看Japex

.size() 必须查看整个列表,而 .isEmpty() 可以在第一个列表中停止。

显然依赖于实现,但正如之前所说,如果您不需要知道实际大小,为什么还要计算所有元素呢?

你说:

这里eTime-sTime>eeTime-eTime在所有情况下为什么?

首先,这可能是因为您的测试代码。 您不能同时测试调用 l.size() 和 l.isEmpty() 的速度,因为它们都查询相同的值。 很可能调用 l.size() 已将列表的大小加载到 cpu 缓存中,因此调用 l.isEmpty() 会快得多。

你可以尝试在两个单独的程序中调用 l.size() 几百万次和 l.isEmpty() 几百万次,但理论上编译器可以优化所有这些调用,因为你实际上并没有做任何事情结果。

无论如何,两者之间的性能差异可以忽略不计,尤其是在进行比较后,您需要查看列表是否为空( l.size() == 0 )。 生成的代码很可能看起来几乎完全相似。 正如其他一些海报所指出的,在这种情况下,您希望优化可读性,而不是速度。

编辑:我自己测试过。 这几乎是一个折腾。 Vector上使用的size()isEmpty()在长期运行中给出了不同的结果,两者都没有始终如一地击败另一个。 ArrayList上运行时, size()似乎更快,但幅度不大。 这很可能是由于对Vector的访问是同步的,因此在尝试对这些方法的访问进行基准测试时,您真正看到的是同步开销,这可能非常敏感。

这里要注意的是,当您尝试优化执行时间相差几纳秒的方法调用时,那么您做错了 首先掌握基础知识,例如在应该使用long的地方使用Long

对链表中的项目进行计数可能会非常慢。

鉴于这两种实现,速度应该是相同的,这是真的。

但到目前为止,这些并不是这些方法的唯一可能实现。 例如,原始链表(不单独存储大小的链表)可以比size()调用更快地回答isEmpty()

更重要的是: isEmpty()准确地描述了您的意图,而size()==0是不必要的复杂(当然不是非常复杂,但应该避免任何不必要的复杂性)。

根据 PMD(基于静态规则集的 Java 源代码分析器),isEmpty() 是首选。 您可以在此处找到 PMD 规则集。 搜索“UseCollectionIsEmpty”规则。

http://pmd.sourceforge.net/rules/design.html

据我说,它还有助于保持整个源代码的一致性,而不是一半的人使用 isEmpty() 而其余的人使用 size()==0。

一般不可能说哪个更快,因为这取决于您使用的接口List的实现。

假设我们正在谈论ArrayList 查找ArrayList的源代码,您可以在 JDK 安装目录的src.zip文件中找到它。 方法isEmptysize的源代码如下所示(Sun JDK 1.6 update 16 for Windows):

public boolean isEmpty() {
    return size == 0;
}

public int size() {
    return size;
}

您可以很容易地看到isEmpty()size() == 0这两个表达式都将归结为完全相同的语句,因此一个肯定不会比另一个快。

如果您对它在接口List的其他实现中的工作方式感兴趣,请自行查找源代码并找出答案。

list.size() 将通过遍历一个又一个索引来计算元素的数量。 list.isEmpty() 将检查第一个元素并确定它不为空并立即给出结果。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM