简体   繁体   English

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

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

Why is list.size()>0 slower than list.isEmpty() in Java?为什么list.size()>0在 Java 中比list.isEmpty()慢? On other words why isEmpty() is preferable over size()>0 ?换句话说,为什么isEmpty()size()>0更可取?

When I look at the implementation in ArrayList , then it looks like the speed should be the same:当我查看ArrayList中的实现时,看起来速度应该是一样的:

ArrayList.size() 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() 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;
     }

If we just write a simple program to get the time take by both the methods, that case size() will take more isEmpty() in all cases, why this so?如果我们只是编写一个简单的程序来获取这两种方法所花费的时间,那么 case size() isEmpty() ,为什么会这样呢?

Here is my TestCode;这是我的测试代码;

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);
    }
}

Here eTime-sTime>eeTime-eTime in all cases.这里eTime-sTime>eeTime-eTime在所有情况下。 Why?为什么?

For ArrayList , yes — you are correct that the operations take (roughly) the same time.对于ArrayList ,是的——你是正确的,操作需要(大致)相同的时间。

For other implementations of List — for example, a naïve linked list* — counting the size might take a very long time, while you only actually care whether it is greater than zero.对于List的其他实现——例如,一个简单的链表*——计算大小可能需要很长时间,而您实际上只关心它是否大于零。

So if you absolutely know that the list is an implementation of ArrayList and will never ever change, then it does not really matter;因此,如果您绝对知道列表是ArrayList的实现并且永远不会改变,那么这并不重要; but:但:

  1. This is bad programming practice to tie yourself down to a specific implementation.这是将自己束缚在特定实现上的不好的编程习惯。
  2. If things change a few years down the line with code restructuring, testing will show that "it works," but things are running less efficiently than before.如果几年后随着代码重组而发生变化,测试将表明“它有效”,但运行效率比以前低了。
  3. Even in the best case, size() == 0 is still not faster than isEmpty() , so there is no compelling reason to ever use the former.即使在最好的情况下, size() == 0仍然不比isEmpty(),因此没有令人信服的理由使用前者。
  4. isEmpty() is a clearer definition of what it is you actually care about and are testing, and so makes your code a bit more easily understandable. isEmpty()更清晰地定义了您真正关心和正在测试的内容,因此使您的代码更容易理解。

* I originally wrote LinkedList here, implicitly referencing java.util.LinkedList , though that particular implementation does store its size explicitly, making size() an O(1) operation here. * 我最初在这里编写 LinkedList ,隐式引用java.util.LinkedList ,尽管该特定实现确实显式存储了它的大小,使size()在这里成为 O(1) 操作。 A naïve linked list operation might not do this, and in the more general sense there is no efficiency guarantee on implementations of List .一个简单的链表操作可能不会这样做,并且在更一般的意义上, List的实现没有效率保证。

Your testing code is flawed.您的测试代码有缺陷。

Just reverse the order, ie call isEmpty first and size > 0 second and you'll get the opposite result.只需颠倒顺序,即首先调用 isEmpty 和 size > 0 秒,您将得到相反的结果。 This is due to class loading, caching, etc.这是由于类加载、缓存等造成的。

I'm sorry, but your benchmark is flawed.对不起,你的基准有缺陷。 Take a look at Java theory and practice: Anatomy of a flawed microbenchmark for a general description on how to approach benchmarks.看一下Java 理论和实践:有缺陷的微基准的剖析,以获得有关如何处理基准的一般描述。


Update : for a proper benchmark you should look into JApex .更新:为了获得适当的基准,您应该查看Japex

.size() has to look at the entire list, while .isEmpty() can stop at the first one. .size() 必须查看整个列表,而 .isEmpty() 可以在第一个列表中停止。

Obviously implementation dependent, but as has been said before, if you don't need to know the actual size, why bother counting all the elements?显然依赖于实现,但正如之前所说,如果您不需要知道实际大小,为什么还要计算所有元素呢?

You said:你说:

Here eTime-sTime>eeTime-eTime in all cases Why?这里eTime-sTime>eeTime-eTime在所有情况下为什么?

First off, it's probably because of your testing code.首先,这可能是因为您的测试代码。 You can't test the speed of calling l.size() and l.isEmpty() at the same time, since they both query the same value.您不能同时测试调用 l.size() 和 l.isEmpty() 的速度,因为它们都查询相同的值。 Most likely calling l.size() has loaded the size of your list into your cpu cache and calling l.isEmpty() is a lot faster as a result.很可能调用 l.size() 已将列表的大小加载到 cpu 缓存中,因此调用 l.isEmpty() 会快得多。

You could try calling l.size() a couple of million times and l.isEmpty() a couple of million times in two separate programs but in theory the compiler could just optimize away all those calls since you're not actually doing anything with the results.你可以尝试在两个单独的程序中调用 l.size() 几百万次和 l.isEmpty() 几百万次,但理论上编译器可以优化所有这些调用,因为你实际上并没有做任何事情结果。

In any case, the performance difference between the two will be negligible, especially once you do the comparison you need to do to see if the list is empty ( l.size() == 0 ).无论如何,两者之间的性能差异可以忽略不计,尤其是在进行比较后,您需要查看列表是否为空( l.size() == 0 )。 Most likely the generated code will look almost completely similar.生成的代码很可能看起来几乎完全相似。 As some other posters noted, you want to optimize for readability in this case, not speed.正如其他一些海报所指出的,在这种情况下,您希望优化可读性,而不是速度。

edit: I tested it myself.编辑:我自己测试过。 It's pretty much a toss-up.这几乎是一个折腾。 size() and isEmpty() used on Vector gave differing results on long runs, neither beat the other consistently. Vector上使用的size()isEmpty()在长期运行中给出了不同的结果,两者都没有始终如一地击败另一个。 When run on an ArrayList size() seemed faster, but not by much.ArrayList上运行时, size()似乎更快,但幅度不大。 This is most likely due to the fact that access to Vector is synchronized, so what you're really seeing when trying to benchmark access to these methods is synchronisation overhead, which can be very sensitive.这很可能是由于对Vector的访问是同步的,因此在尝试对这些方法的访问进行基准测试时,您真正看到的是同步开销,这可能非常敏感。

The thing to take away here is that when you're trying to optimize a method call with a couple nanoseconds difference in execution time, then you're doing it wrong .这里要注意的是,当您尝试优化执行时间相差几纳秒的方法调用时,那么您做错了 Get the basics right first, like using Long s where you should be using long .首先掌握基础知识,例如在应该使用long的地方使用Long

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

Given those two implementations, the speed should be the same, that much is true.鉴于这两种实现,速度应该是相同的,这是真的。

But those are by far not the only possible implementations for these methods.但到目前为止,这些并不是这些方法的唯一可能实现。 A primitive linked list (one that doesn't store the size separately) for example could answer isEmpty() much faster than a size() call.例如,原始链表(不单独存储大小的链表)可以比size()调用更快地回答isEmpty()

More importantly: isEmpty() describes your intent exactly, while size()==0 is unnecessarily complex (not hugely complex of course, but any unnecessary complexity at all should be avoided).更重要的是: isEmpty()准确地描述了您的意图,而size()==0是不必要的复杂(当然不是非常复杂,但应该避免任何不必要的复杂性)。

According to PMD ( static ruleset based Java source code analyzer ) isEmpty() is preferred.根据 PMD(基于静态规则集的 Java 源代码分析器),isEmpty() 是首选。 You can find the PMD ruleset here.您可以在此处找到 PMD 规则集。 Search for "UseCollectionIsEmpty" rule.搜索“UseCollectionIsEmpty”规则。

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

According to me it also helps in keeping the entire source code consistent rather than half of the folks using isEmpty() and the rest using size()==0.据我说,它还有助于保持整个源代码的一致性,而不是一半的人使用 isEmpty() 而其余的人使用 size()==0。

It is impossible to say in general which is faster, because it depends on which implementation of interface List you are using.一般不可能说哪个更快,因为这取决于您使用的接口List的实现。

Suppose we're talking about ArrayList .假设我们正在谈论ArrayList Lookup the source code of ArrayList , you can find it in the file src.zip in your JDK installation directory.查找ArrayList的源代码,您可以在 JDK 安装目录的src.zip文件中找到它。 The source code of the methods isEmpty and size looks as follows (Sun JDK 1.6 update 16 for Windows):方法isEmptysize的源代码如下所示(Sun JDK 1.6 update 16 for Windows):

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

public int size() {
    return size;
}

You can easily see that both expressions isEmpty() and size() == 0 will come down to exactly the same statements, so one is certainly not faster than the other.您可以很容易地看到isEmpty()size() == 0这两个表达式都将归结为完全相同的语句,因此一个肯定不会比另一个快。

If you're interested in how it works for other implementations of interface List , look up the source code yourself and find out.如果您对它在接口List的其他实现中的工作方式感兴趣,请自行查找源代码并找出答案。

list.size() will calculate number of elements by traversing one after another index. list.size() 将通过遍历一个又一个索引来计算元素的数量。 list.isEmpty() will check the first element and decide that it is not empty and give the result immediately. list.isEmpty() 将检查第一个元素并确定它不为空并立即给出结果。

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

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