为什么在Java中list.size()> 0比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()

1
2
3
4
5
6
7
8
    /**
     * Returns the number of elements in this list.
     *
     * @return the number of elements in this list
     */

    public int size() {
      return size;
    }

ArrayList.isEmpty()

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

如果我们只编写一个简单的程序来获取这两种方法所花费的时间,那么在所有情况下,这种情况size()都会花费更多的isEmpty(),为什么呢?

这是我的TestCode;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 a的其他实现,未计算链接的列表* a可能需要很长时间,而实际上您实际上只是在担心它是否大于零。

因此,如果您完全知道该列表是ArrayList的实现,并且永远不会更改,那么它并不重要。但是:

  • 这是将自己束缚于特定实现的不良编程实践。
  • 如果几年后代码重组发生了变化,测试将表明它"行得通",但运行效率却比以前低。
  • 即使在最佳情况下,size() == 0仍然不比isEmpty()快,因此没有令人信服的理由使用前者。
  • isEmpty()是您实际上关心和正在测试的内容的更清晰定义,因此使您的代码更易于理解。
  • *我最初是在这里写LinkedList的,它隐式地引用了java.util.LinkedList,尽管该特定实现确实明确存储了它的大小,使size()在这里成为O(1)操作。单纯的链表操作可能不会这样做,并且从更一般的意义上讲,List的实现没有效率保证。


    您的测试代码有缺陷。

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


    很抱歉,您的基准测试存在缺陷。看看Java理论和实践:一个有缺陷的微基准的剖析,以获取有关如何接近基准的一般描述。

    更新:要获得适当的基准,您应该研究Japex。


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

    显然依赖于实现,但是正如前面已经说过的,如果您不需要知道实际大小,为什么还要麻烦计算所有元素?


    您说过:

    Here eTime-sTime>eeTime-eTime in all cases Why?

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

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

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

    edit:我自己进行了测试。这几乎是一个折腾。在Vector上使用的size()isEmpty()在长期运行中得出不同的结果,两者均未持续击败对方。在ArrayListsize()上运行时,速度似乎更快,但并没有提高很多。这很可能是由于对Vector的访问已同步的事实,因此,在尝试对这些方法的访问进行基准测试时,您真正看到的是同步开销,这可能非常敏感。

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


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

    但是,到目前为止,这些方法并不是唯一可行的方法。例如,原始链表(不单独存储大小)可以比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的源代码如下所示(用于Windows的Sun JDK 1.6更新16):

    1
    2
    3
    4
    5
    6
    7
    public boolean isEmpty() {
        return size == 0;
    }

    public int size() {
        return size;
    }

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

    如果您对接口List的其他实现的工作方式感兴趣,请自己查找源代码并查找。