关于LinkedList和ArrayList的remove方法的效率问题

LonelyCoder2012 2014-02-09 05:41:36
以前一直信奉教科书上的一句话,链表元素删除只涉及指针变动,数组元素删除涉及到元素移动,所以链表元素删除速度比数组元素快。

今天发现貌似没理解对,如果要删除的元素是已知索引且该索引是随机给定的,那么:
对于链表:
要先迭代寻址,再修改指针;
对于数组:
直接删除索引处元素,移动后续元素。

此外,经我测试,对于同样大小的ArrayList和LinkedList,如果进行相同次数的删除操作,那么从尾部删除和从中部删除ArrayList的效率更高,从首部删除则是LinkedList效率更高。
附上大小为100000,操作次数10000的测试结果:
ArrayList尾部删除耗时:1ms
LinkedList尾部删除耗时:4ms
ArrayList中部删除耗时:91ms
LinkedList中部删除耗时:13191ms
ArrayList首部删除耗时:211ms
LinkedList首部删除耗时:2ms
为什么中部删除效率差别这么夸张,甚至比尾部删除还大?

这样看来似乎教科书上那句话就不是绝对的了,在选择ArrayList还是LinkedList效率高这个问题上,决定性因素好像应该是待删除的元素在整个集合中是什么位置,而不是见到remove操作就选择LinkedList,不知我的理解是否正确?

能不能请各位帮我从底层分析一下这个现象产生的原因?我在寻址、移动元素在硬件层面所涉及到的操作概念比较模糊。据说相邻内存空间数据移动很快?
...全文
1051 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
sunbo624 2014-02-12
  • 打赏
  • 举报
回复
希望你能搞清楚 你只比较remove的性能 前提是要保证公平 用下标 本身就不公平 因为这涉及到random access性能 如果你用remove对象 才叫公平 因为都要从头开始找 另外你list中没有重复元素 这是测试缺陷 你没有考虑这个问题 另外你集合中的元素是Integer 也有问题 移动int元素 时间是可以忽略的 应该在集合里放一些大对象 这样才能比较真实的性能 另外 你的内存情况也显得不足 因为你现在是内存充足的情况 所以很容易找到连续空间 综上 这是个失败的测试 这两种数据结构的特点 不是你能质疑的
Lsheep 2014-02-11
  • 打赏
  • 举报
回复
看了你的代码,仔细想了一下,应该是这样的。 删除尾部时,array和link差不多,都很快,是因为array不用移动元素,link可以直接定位到尾部,然后删除。 删除中部时,array比较慢,link非常慢,array每次删除需要移动一半的元素,link定位耗时相当大(每次都要重新定位到中部),由此可见,数据量较大时,定位的开销比移动元素的开销大的多。 删除首部时,array较慢,link很快,array每次删除都要移动所有的元素,而link可以直接定位到首部,直接删除。
七神之光 2014-02-11
  • 打赏
  • 举报
回复
LinkedList基于双向链表 你用的好像是remove(index)。。。
LonelyCoder2012 2014-02-10
  • 打赏
  • 举报
回复
引用 6 楼 sunbo624 的回复:
你测试是用的下标吧 remove(index) 这样删除中间元素一定是arraylist效率高 因为不用遍历了 书上说的 应该是remove(object)这种方法 两种类型都要找元素 这样linkedlist效率就高
那尾部删除为什么依然是ArrayList效率高呢
sunbo624 2014-02-10
  • 打赏
  • 举报
回复
你测试是用的下标吧 remove(index) 这样删除中间元素一定是arraylist效率高 因为不用遍历了 书上说的 应该是remove(object)这种方法 两种类型都要找元素 这样linkedlist效率就高
LonelyCoder2012 2014-02-10
  • 打赏
  • 举报
回复
引用 4 楼 fangmingshijie 的回复:
arraylist是基于动态数组的数据结构,linkedlist基于链表的数据结构。随机访问ArrayList效率高于linkedlist。remove操作linkelist高于arraylist,arraylist要移动数据的。这是数组的特性。
版主大人,你木有看清楚我的问题啊。。。
  • 打赏
  • 举报
回复
arraylist是基于动态数组的数据结构,linkedlist基于链表的数据结构。随机访问ArrayList效率高于linkedlist。remove操作linkelist高于arraylist,arraylist要移动数据的。这是数组的特性。
LonelyCoder2012 2014-02-10
  • 打赏
  • 举报
回复
引用 2 楼 kiyoki 的回复:
LinkedList遇上你这种随机索引是要吐血的啊,又不是数组存储不可以randomAccess,只能一个一个读下去 中部最耗时的原因你可以看jdk源码,位置小于一般长度从头读起大于一半长度从尾读起,自然最费时是读到中间
既然LinkedList位置大于一半长度从尾读起,那为什么尾部删除依然是ArrayList完胜LinkedList。。。比如大小为1000W,操作次数1000W时,测试结果: ArrayList尾部删除耗时:57ms LinkedList尾部删除耗时:156ms
kiyoki 2014-02-10
  • 打赏
  • 举报
回复
引用 16 楼 jplx10001 的回复:
[quote=引用 11 楼 kiyoki 的回复:] [quote=引用 3 楼 LonelyCoder2012 的回复:] [quote=引用 2 楼 kiyoki 的回复:] LinkedList遇上你这种随机索引是要吐血的啊,又不是数组存储不可以randomAccess,只能一个一个读下去 中部最耗时的原因你可以看jdk源码,位置小于一般长度从头读起大于一半长度从尾读起,自然最费时是读到中间
既然LinkedList位置大于一半长度从尾读起,那为什么尾部删除依然是ArrayList完胜LinkedList。。。比如大小为1000W,操作次数1000W时,测试结果: ArrayList尾部删除耗时:57ms LinkedList尾部删除耗时:156ms[/quote] 如果是删最后一个元素,ArrayList删除只是将最后的元素置空,然后将size减少,LinkedList是进入元素所在节点,然后找到前一个节点,将前一个节点的next指向置空,ArrayList动作似乎更少 可以试一下删除倒数第x个的元素,触发ArrayList数组的复制操作试验下 [/quote] 我是楼主。。。上面三连了回复不了。。。 试了一下,我发现LinkedList遍历耗时要比ArrayList数组复制耗时要多得多。。。据我估计,LinkedList删除效率比ArrayList高的索引阀值应该是小于总数的一半的某个值,只有索引比这个阀值小的时候,LinkedList的删除操作效率才有优势。。。前提是都是随机访问。。。 请指正![/quote] LinkedList对于随机访问本身就弱 ArrayList的内部数组是一个native method,不同架构效率可能不同 不一定和size一半有关系 楼上有个楼都说了,LinkedList删除快针对于remove(Object o)的情况,此情况下两种List都要遍历后才可以删除,LinkedList优势就很明显了 对于不同情况采取不同策略才是正道。
jplx10001 2014-02-10
  • 打赏
  • 举报
回复
引用 11 楼 kiyoki 的回复:
[quote=引用 3 楼 LonelyCoder2012 的回复:] [quote=引用 2 楼 kiyoki 的回复:] LinkedList遇上你这种随机索引是要吐血的啊,又不是数组存储不可以randomAccess,只能一个一个读下去 中部最耗时的原因你可以看jdk源码,位置小于一般长度从头读起大于一半长度从尾读起,自然最费时是读到中间
既然LinkedList位置大于一半长度从尾读起,那为什么尾部删除依然是ArrayList完胜LinkedList。。。比如大小为1000W,操作次数1000W时,测试结果: ArrayList尾部删除耗时:57ms LinkedList尾部删除耗时:156ms[/quote] 如果是删最后一个元素,ArrayList删除只是将最后的元素置空,然后将size减少,LinkedList是进入元素所在节点,然后找到前一个节点,将前一个节点的next指向置空,ArrayList动作似乎更少 可以试一下删除倒数第x个的元素,触发ArrayList数组的复制操作试验下 [/quote] 我是楼主。。。上面三连了回复不了。。。 试了一下,我发现LinkedList遍历耗时要比ArrayList数组复制耗时要多得多。。。据我估计,LinkedList删除效率比ArrayList高的索引阀值应该是小于总数的一半的某个值,只有索引比这个阀值小的时候,LinkedList的删除操作效率才有优势。。。前提是都是随机访问。。。 请指正!
LonelyCoder2012 2014-02-10
  • 打赏
  • 举报
回复
引用 10 楼 Lsheep 的回复:
把你测试代码贴出来看看,理论上不会出现你说的那个结果。
代码如下,比较丑。。。见谅:

		int capacity = 100000, delCount = 10000;
		List<Integer> a1List = new ArrayList<Integer>(),
				a2List = new ArrayList<Integer>(),
				a3List = new ArrayList<Integer>(),
				l1List = new LinkedList<Integer>(),
				l2List = new LinkedList<Integer>(),
				l3List = new LinkedList<Integer>();
		
		for( int i = 0; i < capacity; i++ ) {
			a1List.add( i );
			a2List.add( i );
			a3List.add( i );
			l1List.add( i );
			l2List.add( i );
			l3List.add( i );
		}
		
		long start = 0, duration = 0;
		
		start = System.currentTimeMillis();
		for( int i = 0; i < delCount; i++ ) {
			a1List.remove( capacity - i - 1 );
		}
		duration = System.currentTimeMillis() - start;
		System.out.println( "ArrayList尾部删除耗时:" + duration + "ms");
		
		start = System.currentTimeMillis();
		for( int i = 0; i < delCount; i++ ) {
			l1List.remove( capacity - i - 1 );
		}
		duration = System.currentTimeMillis() - start;
		System.out.println( "LinkedList尾部删除耗时:" + duration + "ms");
		
		start = System.currentTimeMillis();
		for( int i = 0; i < delCount; i++ ) {
			a2List.remove( ( capacity - i - 1 ) / 2 );
		}
		duration = System.currentTimeMillis() - start;
		System.out.println( "ArrayList中部删除耗时:" + duration + "ms" );
		
		start = System.currentTimeMillis();
		for( int i = 0; i < delCount; i++ ) {
			l2List.remove( ( capacity - i -1 ) / 2 );
		}
		duration = System.currentTimeMillis() - start;
		System.out.println( "LinkedList中部删除耗时:" + duration + "ms" );
		
		start = System.currentTimeMillis();
		for( int i = 0; i < delCount; i++ ) {
			a3List.remove( 0 );
		}
		duration = System.currentTimeMillis() - start;
		System.out.println( "ArrayList首部删除耗时:" + duration + "ms" );
		
		start = System.currentTimeMillis();
		for( int i = 0; i < delCount; i++ ) {
			l3List.remove( 0 );
		}
		duration = System.currentTimeMillis() - start;
		System.out.println( "LinkedList首部删除耗时:" + duration + "ms" );
LonelyCoder2012 2014-02-10
  • 打赏
  • 举报
回复
引用 9 楼 dibaotianjing 的回复:
你的数据准确不 测试了多少次?
应该准确,测试次数我写了啊。。。1楼的大小10W,操作次数1W;3楼的大小和操作次数都是1000W
LonelyCoder2012 2014-02-10
  • 打赏
  • 举报
回复
引用 8 楼 sunbo624 的回复:
[quote=引用 7 楼 LonelyCoder2012 的回复:] [quote=引用 6 楼 sunbo624 的回复:] 你测试是用的下标吧 remove(index) 这样删除中间元素一定是arraylist效率高 因为不用遍历了 书上说的 应该是remove(object)这种方法 两种类型都要找元素 这样linkedlist效率就高
那尾部删除为什么依然是ArrayList效率高呢[/quote] 尾部和中部有什么区别 ArrayList用下标没差[/quote] 按2楼那位仁兄的说法,LinkedList当索引超过长度一般时是从尾部开始查找的,于是LinkedList的尾部删除和中部删除当然就不一样了。
王二北 2014-02-10
  • 打赏
  • 举报
回复
这种问题,看看源码就明了了, ArrayList底层就是一个Object[]数组,数组都是定长的,那么ArrayList怎么实现动态变长呢,那就是在需要时(比如添加新元素,原始的数组长度不够了),创建一个新的数组,然后把原始数据和新插入的数据copy到新的数组里面,如果是删除的话,删除项后面有元素时,后面的元素都有向前移动。 LinkedList底层实现不再是Object数组,而是一个单向的带头的双向链表(LinkedList的内部类Entry)。删除元素时,只需要修改要删除项的前一个元素的next引用和后一个元素的prev引用即可。 所以在删除方面,LinkedList要比ArrayList效率高一些。 数据结构好久没看过了,大致是这个意思吧,说的不对的地方,其他同志请指正。
kiyoki 2014-02-10
  • 打赏
  • 举报
回复
引用 3 楼 LonelyCoder2012 的回复:
[quote=引用 2 楼 kiyoki 的回复:] LinkedList遇上你这种随机索引是要吐血的啊,又不是数组存储不可以randomAccess,只能一个一个读下去 中部最耗时的原因你可以看jdk源码,位置小于一般长度从头读起大于一半长度从尾读起,自然最费时是读到中间
既然LinkedList位置大于一半长度从尾读起,那为什么尾部删除依然是ArrayList完胜LinkedList。。。比如大小为1000W,操作次数1000W时,测试结果: ArrayList尾部删除耗时:57ms LinkedList尾部删除耗时:156ms[/quote] 如果是删最后一个元素,ArrayList删除只是将最后的元素置空,然后将size减少,LinkedList是进入元素所在节点,然后找到前一个节点,将前一个节点的next指向置空,ArrayList动作似乎更少 可以试一下删除倒数第x个的元素,触发ArrayList数组的复制操作试验下
kiyoki 2014-02-10
  • 打赏
  • 举报
回复
LinkedList遇上你这种随机索引是要吐血的啊,又不是数组存储不可以randomAccess,只能一个一个读下去 中部最耗时的原因你可以看jdk源码,位置小于一般长度从头读起大于一半长度从尾读起,自然最费时是读到中间
Lsheep 2014-02-10
  • 打赏
  • 举报
回复
把你测试代码贴出来看看,理论上不会出现你说的那个结果。
查看余e 2014-02-10
  • 打赏
  • 举报
回复
你的数据准确不 测试了多少次?
sunbo624 2014-02-10
  • 打赏
  • 举报
回复
引用 7 楼 LonelyCoder2012 的回复:
[quote=引用 6 楼 sunbo624 的回复:] 你测试是用的下标吧 remove(index) 这样删除中间元素一定是arraylist效率高 因为不用遍历了 书上说的 应该是remove(object)这种方法 两种类型都要找元素 这样linkedlist效率就高
那尾部删除为什么依然是ArrayList效率高呢[/quote] 尾部和中部有什么区别 ArrayList用下标没差

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧