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

为什么在Java中list.size()>0list.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() ,为什么这样呢?

这是我的testing代码;

 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 。 为什么?

您的testing代码有缺陷。

只要颠倒顺序,即先调用isEmpty,size> 0秒,就会得到相反的结果。 这是由于类加载,caching等

ArrayList来说 ,是的 – 你说得对,这些操作大致是在同一时间。

对于其他实现列表天真链表*,例如,计算大小可能需要很长时间,而您只关心它是否大于零。

所以如果你完全知道列表是ArrayList一个实现,并且永远不会改变,那么它并不重要。 但:

  1. 无论如何,这是糟糕的编程实践,以至于无法将自己与特定的实现联系起
  2. 如果事情随着代码重组而改变几年,testing会显示“有效”,但事情运行效率比以前低
  3. 即使在最好的情况下, size() == 0仍然不会isEmpty() ,所以没有使用前者的强制理由。
  4. isEmpty更清晰地定义了你真正关心和testing的内容,从而使你的代码更容易理解。

(另外,我会在您的问题标题中修改NULL的使用;问题本身和这些操作与任何对象引用是否为空都没有任何关系。)

*我最初在这里写了LinkedList ,隐含地引用了java.util.LinkedList,尽pipe这个特定的实现确实存储了它的长度,使size()成为一个O(1)操作。 一个更天真的链表操作可能不会这样做,从更一般的意义上来说,List的实现没有效率保证。

我很抱歉,但你的基准是有缺陷的。 看一下Java理论和实践:parsing一个有缺陷的微基准,以便对如何实现基准进行一般的描述。


更新 :为了一个适当的基准,你应该看看Japex 。

你说:

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

首先,这可能是因为你的testing代码。 您不能同时testing调用l.size()和l.isEmpty()的速度,因为它们都查询相同的值。 很可能调用l.size()已经把你的列表的大小加载到你的cpucaching中,并且调用l.isEmpty()的速度要快得多。

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

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

编辑:我自己testing。 这几乎是一个折腾。 Vector上使用的size()isEmpty()在长时间运行上给出了不同的结果,但是并没有一直打败对方。 在ArrayList上运行的时候size()似乎比较快,但不是太多。 这很可能是因为Vector访问是同步的,所以当你试图访问这些方法时,你真正看到的是同步开销,这可能是非常敏感的。

这里要拿走的是当你试图优化一个方法调用,在执行时间差几个纳秒时,那么你做错了 。 首先获得基本知识,就像使用Long应该使用long

鉴于这两种实现,速度应该是一样的,这是事实。

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

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

计算链接列表中的项目可能会非常缓慢。

根据PMD(基于静态规则集的Java源代码分析器),isEmpty()是首选。 您可以在这里findPMD规则集。 search“UseCollectionIsEmpty”规则。

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

根据我的说法,这也有助于保持整个源代码的一致性,而不是使用isEmpty()的剩余部分,剩下的使用size()== 0。

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

显然实现依赖,但如前所述,如果你不需要知道实际的大小,为什么要计算所有的元素呢?

一般来说这是不可能的,因为它取决于你正在使用的接口List哪个实现。

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

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

你可以很容易地看到,两个expression式isEmpty()size() == 0将会归结为完全相同的语句,所以一个expression式肯定不会比另一个快。

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