为什么在Java中list.size()> 0比list.isEmpty()慢?
为什么在Java中list.size()>0
比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()
,为什么这样呢?
这是我的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
一个实现,并且永远不会改变,那么它并不重要。 但:
- 无论如何,这是糟糕的编程实践,以至于无法将自己与特定的实现联系起
- 如果事情随着代码重组而改变几年,testing会显示“有效”,但事情运行效率比以前低
- 即使在最好的情况下,
size() == 0
仍然不会比isEmpty()
快 ,所以没有使用前者的强制理由。 -
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它。 这些方法的源代码是isEmpty
, size
如下所示(用于Windows的Sun JDK 1.6 update 16):
public boolean isEmpty() { return size == 0; } public int size() { return size; }
你可以很容易地看到,两个expression式isEmpty()
和size() == 0
将会归结为完全相同的语句,所以一个expression式肯定不会比另一个快。
如果您对其他接口List
实现工作感兴趣,请自己查找源代码并找出答案。