Java 编译器如何处理for循环中的size方法

Java 编译器如何处理for循环中的size方法,java,arraylist,Java,Arraylist,假设list是ArrayList,下面两个代码段中哪一个更快 for(int i=0; i<list.size();i++){...} 澄清 在for循环中,编译器每次调用list.size(),还是调用一次并随后使用它 注意对list.size()的每次调用实际上都会对项目进行计数。这就是问题的本质。任何一个好的编译器都会将这些编译成完全相同的汇编代码。int count=list.size(); int count = list.size(); for(int i=0; i<c

假设
list
是ArrayList,下面两个代码段中哪一个更快

for(int i=0; i<list.size();i++){...}
澄清

在for循环中,编译器每次调用
list.size()
,还是调用一次并随后使用它


注意
list.size()
的每次调用实际上都会对项目进行计数。这就是问题的本质。

任何一个好的编译器都会将这些编译成完全相同的汇编代码。

int count=list.size();
int count = list.size();
for(int i=0; i<count;i++){...}// Is faster

对于(int i=0;i你应该问自己一些问题:

  • 我的表现有问题吗
  • 我是否分析了此代码的瓶颈
如果booth的问题可以用“是”来回答,那么尝试两种方法并描述结果


事实上,我认为这不会有什么区别。

在大多数情况下,速度是无法区分的。也就是说,如果在循环执行时集合可能正在更改,则您的两个循环在语义上是不同的。对集合的修改可能在循环中(或在循环中调用的代码中)或者在并发执行的不同线程中

具体回答“澄清”:是的,每次通过循环都会调用
size
方法

除非我有明确的证据表明优化很重要,否则我永远不会编写这个循环的“优化”版本(集合也不会改变)。如果你的代码经过了如此多的调整,这样的调整会给你带来可测量的加速,你应该会非常高兴。

ArrayList list=new ArrayList();
    ArrayList list = new ArrayList();
            for(int i=0; i<1000000;i++){
                list.add("Object added "+i);
            }
            long startTime =System.currentTimeMillis();
            for(int i=0; i<list.size();i++){
                System.out.println(list.get(i));
            }
            long endTime = System.currentTimeMillis();




            int count=list.size();
            long startTime1 =System.currentTimeMillis();
            for(int i=0; i<count;i++){
                System.out.println(list.get(i));
            }
            long endTime1 = System.currentTimeMillis();
            System.out.println("Exe1 time in millis secs"+(endTime-startTime));
            System.out.println("Exe2 time in millis secs"+(endTime1-startTime1));

OutPut 
...........
.............

Object added 999999
Exe1 time in millis secs14131
Exe2 time in millis secs14106

对于(int i=0;i我认为第二段代码稍微快一些。方法调用可能需要将代码加载到内存中,并将数据从方法内存堆栈加载到方法内存堆栈(如第一段代码)。这显然比单个内存访问(第一段代码)慢但是,如果循环迭代期间数组大小可能发生变化,我认为使用第一段代码更安全。请考虑下面的例子:

for(int i=0; i<list.size();i++){
  if list.get(i) == 0 { list.remove(i); i--; }
}    

用于(int i=0;我在这里谈论的是纳秒……这真的有那么重要吗?我认为可读性应该是你更关心的问题。这个迭代需要纳秒。但是如果它被用于一个非常繁重的算法呢。所有处理繁重算法的有效代码片段都依赖于这些问题。分析两种解决方案,以数字和数字形式获得结果为您的特定平台找到答案。不过,这是一个微观优化,它对占用您的真实世界时间没有任何好处。当您进行优化时,您还可以在循环外初始化
i
,ala C.可能会为您节省几皮秒。不过,说真的,它们应该编译成相同的东西。请注意,每次调用list.size()将实际去计算项目。这就是问题的本质。既然您说您使用
ArrayList
作为
列表
实现,那么您的陈述是错误的。请参阅我的澄清部分否,因为a)java编译器几乎没有进行优化,因为这将降低JIT的好处;b)它将使行为人最终成为关注必须如何解决真正的性能问题的人。这是因为它是(或者至少是:可能是)多线程使用错误。@ChristianKuetbach这不是多线程的问题,列表的大小在任何情况下都是错误的。但是如果列表没有改变,这是一个可行的优化,尽管我会声明count为
final
。我认为在大多数情况下,在没有迭代器()的情况下进行迭代时更改列表将导致ConcurrenModificationException:)@ChristianKuetbach我认为,
当(!list.isEmpty()){list.remove(0);}
在循环构造中调用
size
时,会更改基础数据结构,并且不会引发此异常;-)ConcurrenModificationException将在尝试同时访问迭代器和列表时发生。例如,在循环到迭代器并从列表或映射中删除元素时。在某些情况下,“优化”版本也可能是错误的(以此列表的多线程使用为例)。同意。因此“…您的两个循环在语义上不同…”您使用的是“System.out.println()”的时间,而不是循环。与list.size()的更改相比,使用System.out的速度非常慢。这就是我所说的“profile”。使用profile4j或eclipse profiler并对我的注释进行版本化。鉴于System.out.println的速度慢得惊人,它的速度提升了0.1%,实际上看起来相当不错(假设它是可复制的)。说真的,我的整个3D游戏因为一些打印声明而结结巴巴地停止了
    ArrayList list = new ArrayList();
            for(int i=0; i<1000000;i++){
                list.add("Object added "+i);
            }
            long startTime =System.currentTimeMillis();
            for(int i=0; i<list.size();i++){
                System.out.println(list.get(i));
            }
            long endTime = System.currentTimeMillis();




            int count=list.size();
            long startTime1 =System.currentTimeMillis();
            for(int i=0; i<count;i++){
                System.out.println(list.get(i));
            }
            long endTime1 = System.currentTimeMillis();
            System.out.println("Exe1 time in millis secs"+(endTime-startTime));
            System.out.println("Exe2 time in millis secs"+(endTime1-startTime1));

OutPut 
...........
.............

Object added 999999
Exe1 time in millis secs14131
Exe2 time in millis secs14106
for(int i=0; i<list.size();i++){
  if list.get(i) == 0 { list.remove(i); i--; }
}