颠覆认知的 ArrayList 和 LinkedList(原因未解决)

!!!!感谢 Alector 工程师指导

前言

所有 Java 的童鞋们面试时都会被问过这道题:你说一下集合的特点。

所以 ArrayList 和 LinkedList 的性能问题就一定会被说到:

  • 增删时 LinkedList 快,因为 LinkedList 是链表,增删时只需要在该位置断开链接,分别链到最新的那个对象的前后节点就完成了。
  • ArrayList 相对慢,因为这是一个数组,还涉及到扩容、元素移动的操作。
  • 在 JDK1.8 经过对 ArrayList 优化之后,ArrayList 的增删已经和 LinkedList 的速度不差多少了。

以前我也是这样认为的,并且面试时也是这样说的。但今天,一个笔名为 Alecter 的工程师(我称为廖工)的一番话给我提了个醒,我颠覆了对这个说法的认知。以下是我们的测试结果。

测试代码如下(数据量5w、50w,为了不影响数据,每次运行main只运行一个方法,运行5次main)

1. 中间插入

public class ListMain {


	public static void main(String[] args) {
		insert_5w(new ArrayList<>();
		insert_5w(new LinkedList<>();
		insert_50w(new ArrayList<>();
		insert_50w(new LinkedList<>();
	}


	private static void insertTest_5w(List<Object> list) {
		insert(list, 50000);
	}

	private static void insertTest_50w(List<Object> list) {
		insert(list, 500000);
	}

	private static void insert(List<Object> list, int size){
		long start = System.currentTimeMillis();
		int pos;
		Object o = new Object();
		for (int i = 0; i < size; i++) {
			pos = list.size() / 2;
			list.add(pos, o);
		}
		long end = System.currentTimeMillis();
		System.out.println("用时:" + (end - start));
	}
}
测试次数
ArrayList_5w8977869489
LinkedList_5w16051478152514471409
ArrayList_50w79578441856779458318
LinkedList_50w235089231305我错了大哥

这个可以理解,因为 ArrayList 是有序数组,支持随机访问;而 LinkedList 只能一个一个去找位置,所以这种中间插入法对 LinkedList 不友好。

2. 追加法

方法和上面一样,不啰嗦,只写出实际运行代码

public class ListMain {

	private static void append(List<Object> list, int size){
		long start = System.currentTimeMillis();
		Object o = new Object();
		for (int i = 0; i < size; i++) {
			list.add(pos, o);
		}
		long end = System.currentTimeMillis();
		System.out.println("用时:" + (end - start));
	}
}
测试次数
ArrayList_5w34434
LinkedList_5w44346
ArrayList_50w1312131313
LinkedList_50w1715161618
ArrayList_500w6986747069
LinkedList_500w328309318312312
ArrayList_5000w756746744739799
LinkedList_5000w1924519050250821875321347

不是说 LinkedList 的增删效率比 ArrayList 更高吗?为什么越往后 ArrayList 的效率比 LinkedList 高得越明显了呢?更有,达到 5000w 时几乎高出 30 倍?

先把剩下的先做完实验再总结

3. 头插法

public class ListMain {
	private static void appendFirst(List<Object> list, int size) {
		long start = System.currentTimeMillis();
		Object o = new Object();
		for (int i = 0; i < size; i++) {
			list.add(0, o);
		}
		long end = System.currentTimeMillis();
		System.out.println("用时:" + (end - start));
	}
}
测试次数
ArrayList_5w43645
LinkedList_5w44555
ArrayList_50w1313131512
LinkedList_50w1617161716
ArrayList_500w7371707467
LinkedList_500w313330351405311
ArrayList_5000w817785774777787
LinkedList_5000w1811719016184041836517957

4. 头删法

public class ListMain {

	/**
	 * 此代码复用
	 */
	private static void beforeRemove(List<Object> list, int size){
		Object o = new Object();
		for (int i = 0; i < size; i++) {
			list.add(0, o);
		}
	}

	private static void removeFirst(List<Object> list, int size) {
		beforeRemove(list, size);
		long start = System.currentTimeMillis();
		for (int i = 0; i < list.size(); i++) {
			list.remove(0);
		}
		long end = System.currentTimeMillis();
		System.out.println("用时:" + (end - start));
	}
}
测试次数
ArrayList_5w137148138161149
LinkedList_5w34337
ArrayList_50w1183512167118401203111734
LinkedList_50w1068710

5. 尾删法

	private static void removeLast(List<Object> list, int size) {
		beforeRemove(list, size);
		long start = System.currentTimeMillis();
		for (int i = list.size() - 1; i >= 0; i--) {
			list.remove(0);
		}
		long end = System.currentTimeMillis();
		System.out.println("用时:" + (end - start));
	}
测试次数
ArrayList_5w186174249204200
LinkedList_5w54554
ArrayList_50w1731718114165871852020521
LinkedList_50w141791313

6. 中删法

	private static void removeMod(List<Object> list, int size) {
		beforeRemove(list, size);
		long start = System.currentTimeMillis();
		while (list != null && list.size() > 0) {
			list.remove(list.size() / 2);
		}
		long end = System.currentTimeMillis();
		System.out.println("用时:" + (end - start));
	}
测试次数
ArrayList_5w7712877360107
LinkedList_5w12121245123313941347
ArrayList_50w84847866819778798429
LinkedList_50w133318我错了

综述

经测试发现

  1. LinkedList 是不连续的内存空间,从中间操作需要获取到集合中间的数据再操作,所以这种操作对 LinkedList 不友好。
  2. LinkedList 只是在做数据删除时效率才比 ArrayList 高,数据量上万时不管是前删还是后删,效率高出几十倍甚至上百倍。
  3. 对于增加,实验证明是 ArrayList 的效率比 LinkedList 高,并且在我测试的50万阈值时,数据量越大越明显,即使 ArrayList 涉及到扩容、元素移动等操作。

关于源码

回到开头,在1.8时对 ArrayList 做了优化,但真的是这样吗?为此我翻看了 jdk 1.7 和 jdk 1.8 有关于 ArrayList 的方法(增、删),发现两个版本中对于这部分竟然然不差分毫,并且运行的结果也不相上下……

结论

开头的说法几乎是一边倒的说法,那这个结论什么时候被大众熟知并列为公知的?这个结论当时是怎么证明出来的?为什么这么久没有人对这一说法提出异议?jdk 究竟是在什么时候对 ArrayList 进行的优化?如果真的是 1.8 进行的优化,那优化的逻辑会不会在 jdk 的底层 c++ ?还有多少没经过论证的错误结论在横行、在污染大众公知?

由于我没有找到 jdk 1.6 及更前的版本,暂时无法论证以上的问题,经我和廖工讨论,决定对开头的结果持保留意见。

Alector 工程师开玩笑说:“我可能是学了假的 Java ……”。
或许,我们真的是学了假的 Java ……

后言

不忘初心,牢记使命。
让我们携手,互帮互助,取长补短,一起朝各自的目标奋力迈进!!!

由衷感谢您的观看,您的收获和支持就是我的最大动力!!再次感谢!!

再次感谢廖工的指导!!!

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值