用于循环优化

Joe*_*Joe 45 java optimization coding-style

List<String> flowers = new ArrayList<String>();
Run Code Online (Sandbox Code Playgroud)

我的for循环目前看起来像这样......

for (int i = 0; i < flowers.size(); i++) {
...
}
Run Code Online (Sandbox Code Playgroud)

或者我应该改变它看起来像下面给出的代码

int size = flowers.size();
for (int i = 0; i < size; i++) {
...
}
Run Code Online (Sandbox Code Playgroud)

哪个性能更高(假设我有大量的花),我猜它应该是后者.

Jig*_*shi 57

最好使用for-each循环 [更易读]

for (Flower flower :flowers){
    //...
}
Run Code Online (Sandbox Code Playgroud)

我已经转储使用javap以下代码的说明:

public void forLoop1() {
    List<String> lst = new ArrayList<String>();
    for (int i = 0; i < lst.size(); i++) {
        System.out.println("hi");
    }
}

public void forLoop2() {
    List<String> lst = new ArrayList<String>();
    int size = lst.size();
    for (int i = 0; i < size; i++) {
        System.out.println("hi");
    }
}
Run Code Online (Sandbox Code Playgroud)
public void forLoop1();
  Code:
   0:   new     #2; //class java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   iconst_0
   9:   istore_2
   10:  iload_2
   11:  aload_1
   12:  invokeinterface #4,  1; //InterfaceMethod java/util/List.size:()I
   17:  if_icmpge       34
   20:  getstatic       #5; //Field java/lang/System.out:Ljava/io/PrintStream;
   23:  ldc     #6; //String hi
   25:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   28:  iinc    2, 1
   31:  goto    10
   34:  return

public void forLoop2();
  Code:
   0:   new     #2; //class java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   aload_1
   9:   invokeinterface #4,  1; //InterfaceMethod java/util/List.size:()I
   14:  istore_2
   15:  iconst_0
   16:  istore_3
   17:  iload_3
   18:  iload_2
   19:  if_icmpge       36
   22:  getstatic       #5; //Field java/lang/System.out:Ljava/io/PrintStream;
   25:  ldc     #6; //String hi
   27:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   30:  iinc    3, 1
   33:  goto    17
   36:  return
Run Code Online (Sandbox Code Playgroud)

它不适合我.

java版"1.6.0_22"Java(TM)SE运行时环境(版本1.6.0_22-b04)Java HotSpot(TM)客户端VM(版本17.1-b03,混合模式,共享)

因此,如果您需要从提到的两个中进行选择,请选择第二个,但我个人会选择for-each.


for-each Performance

来自Joshua Bloch的Effective Java中的第46项:

在1.5版中引入的for-each循环通过完全隐藏迭代器或索引变量来消除混乱和错误的机会.由此产生的习语同样适用于集合和数组:

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    doSomething(e);
}
Run Code Online (Sandbox Code Playgroud)

当您看到冒号(:)时,将其读作"in".因此,上面的循环读作"对于元素中的每个元素e".请注意,使用for-each循环没有性能损失,即使对于数组也是如此.实际上,在某些情况下,它可能比普通的for循环提供轻微的性能优势,因为它只计算一次数组索引的限制.虽然您可以手动执行此操作(第45项),但程序员并不总是这样做.


也可以看看

  • *它没有为我优化.* - 仅供参考,javac很少进行优化.像这样的优化是由JIT编译器完成的. (22认同)
  • -1:这不是问题,解释哪个结构最好,这只是一个评论...... (11认同)
  • +1它**是一个很好的答案,因为当你的所有表现都不好时,你应该做的最后一件事就是进行那种微优化.使用多线程并查看复杂性来解决性能问题(O-Notation,包括选择适当的集合类型).坚实的+1为可读性.有时它有助于**而不是**考虑微调.(一般声明,乔可能有充分的理由提出他的问题) (6认同)
  • 这不是一个好的答案.每个循环的Java是OPs问题的两倍慢,是优化循环的4倍. (2认同)
  • 多么可靠的答案!!! 我忙于Effective Java.真是太棒了.唯一需要检查的是花是否为空.但它不适用于上述示例. (2认同)

Dav*_*nco 20

很抱歉,但@ Jigar的回答不正确.这是正确的答案.(tldr;不要使用for : each).

import java.util.ArrayList;
import java.util.List;

public class LoopTest {

    public static void main(String s[]) {

        long start, end;

        List<Integer> a =  new ArrayList<Integer>();

        for (int i = 0; i < 2500000; i++) {
            a.add(i);
        }

        ///// TESTING FOR : EACH LOOP

        start = System.currentTimeMillis();

        for (Integer j : a) {
            int x = j + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ Integer j : a ] ");

        ////// TESTING DEFAULT LOOP

        start = System.currentTimeMillis();
        for (int i = 0; i < a.size(); i++) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = 0; i < a.length; i++ ] ");


        ////// TESTING SLIGHTLY OPTIMIZED LOOP

        start = System.currentTimeMillis();
        int size = a.size();
        for (int i = 0; i < size; i++) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = 0; i < size; i++ ] ");        

        //// TESTING MORE OPTIMIZED LOOP

        start = System.currentTimeMillis();
        for (int i = size; --i >= 0;) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = size; --i >= 0; ] ");       

    }

}
Run Code Online (Sandbox Code Playgroud)

结果:

96 milli seconds for [ Integer j : a ] 
57 milli seconds for [ int i = 0; i < a.length; i++ ] 
31 milli seconds for [ int i = 0; i < size; i++ ] 
31 milli seconds for [ int i = size; --i >= 0; ] 
Run Code Online (Sandbox Code Playgroud)

您可以自己决定,但JVM优化器的归因太多了.你仍然必须聪明地使用自己的代码,使用for : each符号并不是一个好主意(几乎所有).如您所见,通过将大小放在自己的变量中,您有一个好主意.

尽管其中一些优化可能依赖于JVM(有些可能会与JIT相关),但了解Java的作用以及Java不执行的操作非常重要.

  • 我认为你的每个示例代码的问题是自动装箱和拆箱. (10认同)
  • 说"使用for-each符号并不是一个好主意(几乎所有)"是非常愚蠢的.在绝大多数情况下,您会更喜欢可读性而不是性能.在你的循环是性能bottelneck的少数情况下,调查是否有更高性能的选项可能是值得的. (4认同)
  • 你正确的'for:每个`总是很慢并且调用频繁的`.size()`是一些比分配给一个变量慢的东西 (3认同)
  • 尝试使用`LinkedList`运行'基准'. (3认同)

Raz*_*aze 11

JVM无法对其进行优化,因为它size()是一种方法,JVM无法(也不会尝试)确定size()在此上下文中始终返回相同的值.提供的size()值不会改变,第二个值会稍微提高性能,但增益是如此,如此轻微以至于你甚至不必考虑使用它.


Pet*_*rey 9

如果性能至关重要,请使用普通计数器循环,但是对于98%的情况,代码的清晰度和简单性更为重要(如1000x或更多),您应该使用for-each循环.

@David指出使用计数器更快,但我要指出,即使是10,000个条目,差异也是亚微秒.

如果你有超过10,000个条目的集合,很可能你不应该蛮力迭代所有可能性.更像是一个像Map一样的查找集合是一个更好的解决方案,无论你想到什么.如果您的参赛作品远远少于10,000,则表现不太可能变得重要.


Tho*_*ler 6

如果在迭代时数组列表发生更改,则行为会有所不同.但我想你不这样做.根据我的测试,后者通常更快(特别是在像Android这样的系统上).我会写如下:

for (int i = 0, size = flowers.size(); i < size; i++) {
    ...
}
Run Code Online (Sandbox Code Playgroud)