大多数人不清楚的:for循环与foreach循环的性能差距


一、ArrayList中,for循环 VS 增强for

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

public class ArrayListForEach {
	public static void main(String[] args) {
		List<Integer> arrayList = new ArrayList<Integer>();
		// 每个集合插入10万条数据
		for (int i = 0; i < 100000; i++) {
			arrayList.add(i);
		}
		long StartTime;//开始时间
		long EndTime;//结束时间

		System.out.println("============遍历ArrayList============");

		// 用for循环arrayList
		StartTime = System.currentTimeMillis();
		for (int i = 0; i < arrayList.size(); i++) {
			arrayList.get(i);
		}
		EndTime = System.currentTimeMillis();
		System.out.println("for遍历需要:" + (EndTime - StartTime) + "毫秒");

		// 用增强for循环arrayList
		StartTime = System.currentTimeMillis();
		for (Integer in : arrayList) {

		}
		EndTime = System.currentTimeMillis();
		System.out.println("增强for遍历需要:" + (EndTime - StartTime) + "毫秒");
	}
}

运行结果:
在这里插入图片描述
结论:遍历ArrayList时,普通for循环比foreach循环花费的时间要少一点.

二、LinkedList中,for循环 VS 增强for

import java.util.LinkedList;
import java.util.List;

public class LinkedListForEach {
	public static void main(String[] args) {
		List<Integer> linkList = new LinkedList<Integer>();
		// 每个集合插入10万条数据
		for (int i = 0; i < 100000; i++) {
			linkList.add(i);
		}
		long StartTime;
		long EndTime;

		System.out.println("============遍历LinkedList============");

		// 用for循环linkList
		StartTime = System.currentTimeMillis();
		for (int i = 0; i < linkList.size(); i++) {
			linkList.get(i);
		}
		EndTime = System.currentTimeMillis();
		System.out.println("for遍历:" + (EndTime - StartTime) + "毫秒");

		// 用增强for循环linkList
		StartTime = System.currentTimeMillis();
		for (Integer in : linkList) {

		}
		EndTime = System.currentTimeMillis();
		System.out.println("用增强for遍历需要:" + (EndTime - StartTime) + "毫秒");
	}
}

运行结果:
在这里插入图片描述

结论:遍历LinkList时,普通for循环比foreach循环花费的时间要多很多


三、剖析ArrayList中两者性能的原理

我们对上面的代码进行反编译,一探究竟:
在这里插入图片描述
通过反编译代码,我们可以看到,for循环倒是没变化,增强for循环却是变化剧烈,原来增强for循环只是Java提供给我们的语法糖,其底层实现还是for循环。如果只是到这里当然不能叫原理剖析,下面我们继续深入。

1.ArrayList的底层数据结构

首先要明确的是ArrayList只是JDK为我们封装好的类,通过翻看源码,可以发现其底层数据结构就是数组。详细可以参看这篇博文:ArrayList源码详细分析JDK1.8(一)

2.ArrayList中get方法的实现

用for循环遍历ArrayLis集合的过程就是不断调用get()方法的过程,所以我们要知道get()方法是怎么工作的。
在这里插入图片描述
上图,我们可以知道get()方法内部调用了elementDate()方法,而elementDate()方法就是直接通过下标从数组中拿到一个元素并返回。学过数据结构的都知道通过下标在数组中找到指定元素的时间复杂度是O(1)。

3.ArrayList中iterator方法的实现

通过反编译后的代码,我们知道使用增强for循环遍历ArrayList集合的过程有两个关键点。

(一)就是调用了iterator()方法,通过翻看源码,我们发现该方法返回了一个Itr类。源码如下:
在这里插入图片描述
(二)主要依赖next()方法拿元素。
在这里插入图片描述
上面是next()方法的源码,有很多if语句,这些不用管,即使不理解也根本不妨碍,我们聚焦与红色方框标出的部分。这是不是很熟悉呢?????原来next()也是通过下标出数组中拿数据的,那么它的时间复杂度也是O(1)。

4.小结

小结一:对于ArrayList来说,使用for循环和增强for循环性能相差无几的根本原因是:其底层实现都是通过下标从数组中拿数据,每拿一个数据的时间复杂度都是O(1),所以两者性能上没有很大的差距。

小结二:为什么for循环还是要比增强for循环更加快一点呢?get()方法多简洁就一行,而next()方法却有那么多安全性判断,慢一点很正常嘛!

四、剖析LinkedList中两者性能的原理

我们继续对LinkedList情况下代码反编译
在这里插入图片描述

1.LinkedList的底层数据结构

同样LinkedList也是JDK封装好给我们使用的类,其底层数据结构其实是链表(更具体一点是双向链表),详细可以看看这篇文章分析的很透彻:JDK8:LinkedList源码分析

2.LinkedList中get方法的实现

同理,用for循环遍历LinkedLis集合的过程也是不断调用get()方法的过程,所以我们要知道get()方法是怎么工作的。
在这里插入图片描述
上图,我们可以知道get()方法内部调用了node()方法。对于node()方法那就没什么好说的啦,因为是链表,没有下标可用,查找到一个指定元素必须从第一个元素到指定元素挨个遍历一遍。

不过JDK在这个有一个小的优化。首先,“size >> 1”相当于除以2,所以当指定元素在整个链表的前半段的时候从首元素开始遍历,当指定元素在后半段的时候从尾元素开始遍历,可以节约一半时间,所以找到某个指定元素的平均时间复杂度是O(N/4),不过在数据结构中我们认为还是O(N)。

3.LikedList中iterator方法的实现

LinkedList中iterator()方法的实现非常复杂,不是说代码有多难,而是子、父类方法相互调用,还涉及到子、父类的内部类和抽象方法,绕的了一大圈,有兴趣的可以自己亲自理一遍。最终还是找到了其具体代码,如下图所示:
在这里插入图片描述
这个next()方法的实现可比上面那种节约时间多了,就是从第一个元素开始遍历,每调用一次next()方法,就返回当前元素的值并移动到下一个元素,不是每一次都是从头开始查找的,所以每遍历一个元素的时间复杂度是O(1)。

4.小结

小结:为什么在LinkedList中for循环比增强for循环慢那么多?因为,for循环每次都要从头(或尾)查找,每遍历一个元素的时间复杂度是O(N),而增强for循环是返回当前元素并移动到下一个元素,每遍历一个元素的时间复杂度是O(1)。

结束语

在学习过程中搜索了很多for循环和增强for循环性能比较的文章,一些文章开门见山直接给出结论,还是一些更加严谨,会给出测试代码以及测试结果,这样更有说服力。但仅仅但这一步还是无法满足我的好奇心,我就想知道到底是为什么会产生这样的性能差距?结果大部分文章却是没有给出,而且看一些文章的评论,也有其他读者很想知道其中的内情,所以就促成了这篇文章。最后当然跪求三连啊!!!!!!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值