【问题标题】:Is it useful to check if a Java collection is empty before beginning iteration?在开始迭代之前检查 Java 集合是否为空是否有用?
【发布时间】:2014-12-10 01:05:11
【问题描述】:

在下面的两种样式中,分配了一个Iterator 对象。在迭代之前检查集合是否为空是否有用?我不知道这是否符合“过早优化”的条件。希望对 JVM 垃圾收集器有深入了解的人可以提供见解。

另外,我不知道 Java 编译器如何处理 for-each 循环。我假设样式 B 会自动转换为样式 A 。但是...可能包括一张空支票。

循环样式 A

Collection<String> collection = ...
Iterator<String> iter = collection.iterator();
while (iter.hasNext()) {
    String value = iter.next();
    // do stuff
    // maybe call iter.remove()
}

循环样式 B

Collection<String> collection = ...
for (String value : collection) {
    // do stuff
}

循环样式 A(修改)

Collection<String> collection = ...
if (!collection.isEmpty()) {
    Iterator<String> iter = collection.iterator();
    while (iter.hasNext()) {
        String value = iter.next();
        // do stuff
        // maybe call iter.remove()
    }
}

循环样式 B(修改)

Collection<String> collection = ...
if (!collection.isEmpty()) {
    for (String value : collection) {
        // do stuff
    }
}

【问题讨论】:

  • 为什么要检查空,那是最糟糕的防御性编程!检查null是一回事,但检查空是不好的做法。
  • 为什么?我可以看到好处...而不是访问迭代器并检查方法调用以查看是否存在next,这是JVM 将在for(Object obj : objects) 上执行的操作,它只会绕过所有代码,如果集合是空的......据我所知,这是一个优化,现在如果它是一个好的,那是另一回事......
  • 似乎所有现有答案都错过了micro-optimization 标签,这是一个性能不正确的问题。

标签: java collections garbage-collection iteration micro-optimization


【解决方案1】:

是的,如果有帮助的话,这肯定会是一个过早的优化。您的循环必须对性能非常关键,并且通常使用空集合调用,并且由于某种原因无法优化创建实际迭代器对象的大部分成本。

在那场完美风暴中,这种巨大的源头丑陋可能是有道理的。但更有可能的是,您可以重新安排一些东西来帮助编译器更好地优化,比如保持迭代器本地化。


迭代器对象(通常?)是函数本地的,因此创建起来很便宜(可以只存在于寄存器中,不需要堆分配)。请参阅https://www.beyondjava.net/escape-analysis-java 了解有关 JVM 的一些详细信息如果转义分析证明它是纯本地的,则对 Object 进行“标量替换”,其他代码看不到对它的引用。所以可能的节省甚至不包括内存分配。

如果这在做某事之前进行了 JIT 编译到单独的检查,那么即使集合不为空,它也会始终运行额外的指令。

针对最常见的情况进行优化。不要添加额外的代码来稍微加快罕见的空情况,而是将其保留以加速常见的非空情况。

我认为大多数循环倾向于在非空集合上运行。在某些情况下,小很常见,但空的通常很少见。也许您有一个经常或通常在空集合上运行的循环,例如程序中很少使用的功能。然后值得考虑针对这种情况进行优化。 (这是否是一种有用的方法是另一回事)

这个对collection.isEmpty() 的额外调用可能只是优化到循环条件,如果它 JIT 编译为一个简单的指针增量循环在一个数组上,开始和结束指针都保存在寄存器中。这是最好的情况,但额外的源噪声是无用的,无论如何你都会得到什么。


您可能会争辩说,如果for (String value : collection) 尚未编译为循环集合的最有效方式,那是编译器 + JVM 的错误,您不应该让源代码变得丑陋。这在一定程度上可能是正确的,尽管引入对.isEmpty() 的调用不是编译器或运行时可以做的事情,除非他们可以内联该方法以查看它确实检查了迭代器会检查的相同内容。但是使用 JIT 编译,所有内容都可以内联。


TL:DR:一个好的 JIT 编译器很可能不会真正花费任何实际工作为大多数简单集合创建迭代器,并且没有什么可以保存的。

在其他情况下,除非您的循环通常在空集合上运行,或者(甚至更不可能)迭代器创建在某种程度上非常昂贵,否则最好不要这样做(出于性能考虑)。 p>

【讨论】:

    【解决方案2】:

    不,您不必检查是否为空。第一次迭代将为您解决问题。

    【讨论】:

      【解决方案3】:

      iter.hasNext() 方法将返回真/假值。如果集合中没有元素,那么迭代器只会在执行语句 iter.hasNext() 时返回 false,并且循环将正常终止。

      【讨论】:

        【解决方案4】:

        不需要检查集合是否为空。如果您使用 for 循环或使用带有迭代器的 while 循环进行迭代,如果集合为空,它将不会进入迭代。

        但是当你迭代集合时,你应该检查集合是否不为空。如果集合为 null 并且您尝试使用循环或迭代器进行迭代,它可能会抛出 NullPointerException

        你不需要检查集合是否为空。

        【讨论】:

          猜你喜欢
          • 2011-04-24
          • 1970-01-01
          • 2016-06-20
          • 2017-05-28
          • 2013-06-19
          • 1970-01-01
          • 2017-05-07
          • 2018-08-09
          • 2021-11-02
          相关资源
          最近更新 更多