Java中LinkedList的remove方法真的耗时O(1)吗?

这个问题其实来源于Leetcode的一道题目,也就是上一篇日志 LRU Cache。在使用LinkedList超时后,换成ArrayList居然AC了,而问题居然是在于List.remove(Object o)这个方法。

我们知道,链表和数组相比,最主要的特点就是add和remove的操作是O(1)的。Java中的链表一般使用LinkedList这个类型,数组一般使用ArrayList。它们同时implements了List这个interface,所以都有remove(int index)和remove(Object o)这两个方法。

普通意义上认为链表的remove操作是O(1)的,是因为对于某个给定的节点node,可以将它的前置节点的next直接置为node的下一个。而数组,则需要删除index处的元素,再将后面n个元素前移,所以需要O(n)的时间。

但是,在Java中果真如此吗?

细看JDK的源码,就可以发现,LinkedList的remove(int index)和remove(Object o)这两个方法都做不到O(1)的时间,而是O(n)。这是因为上面说的数据结构中的O(1)时间,是对于某个已经确定的节点。而LinkedList中,首先必须通过一个循环,找到第一个出现的Object o,或者走到index这个位置,再进行操作。也就是,有一个get的过程。

这时,虽然ArrayList的remove(int index)和remove(Object o)也是O(n)时间,但是移动耗费的时间远比LinkedList中往后寻址来的快得多,特别是元素很多的时候。JDK的源码里,这个操作是用System.arraycopy()来做的。所以,这时,LinkedList的最为坑爹的地方,也是最令人不解的地方就出现了——remove的操作居然比ArrayList还慢,而且慢的多!

而且,LinkedList需要内部维护一个数据结构,JDK 6中叫Entry,JDK 7中叫Node,这需要很多额外的内存。所以,除非急切需要LinkedList的Deque功能,任何情况下都应该使用ArrayList。其实,即使要用Deque,也有ArrayDeque。

所以,有时面试会问,在一个LinkedList list的遍历for循环中,不断执行remove(i)操作,时间复杂度是多少?其实是O(n^2),而不是O(n)。但是,如果使用iterator,it.remove()的时间复杂度就是O(1)了,因为这时元素已经给定。并且,for循环中进行remove(i)操作是要影响下标的。remove过后每次i都必须i--。使用iterator可以有效避免这个问题。这里可以看到,虽然for循环比较直观,但是有时iterator还是非常好的。

时间: 2024-08-05 19:08:10

Java中LinkedList的remove方法真的耗时O(1)吗?的相关文章

Java中vector的使用方法

Vector的使用 vector类底层数组结构的,它包含可以使用整数索引进行访问的组件.不过,vector的大小可以根据需要增大或缩小,以适应创建vector后进行添加或移除项的操作,因此不需要考虑元素是否越界或者会不会浪费内存的问题. 由vector的iterator和listIterator方法所返回的迭代器是快速失败的:也即是它不能并发执行操作.如果在迭代器创建后的任意时间从结构上修改了向量(通过迭代器自身的remove或add方法之外的任何其他方式),则迭代器将抛出ConcurrentM

Java中Integer类的方法

字段摘要 static int MAX_VALUE              保持 int 类型的最大值的常量可取的值为 231-1. static int MIN_VALUE              保持 int 类型的最小值的常量可取的值为 -231. static int SIZE              以二进制补码形式表示 int 值的位数. static Class<Integer> TYPE              表示基本类型 int 的 Class 实例. 构造方法摘要

java中的jdbc连接数据库方法及应用

jdbc连接数据库的口诀:猪脸特直观 import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class Demo03 { public static void main(String[] args) thr

java中substring的使用方法

str=str.substring(int beginIndex);截取掉str从首字母起长度为beginIndex的字符串,将剩余字符串赋值给str: str=str.substring(int beginIndex,int endIndex);截取str中从beginIndex開始至endIndex结束时的字符串,并将其赋值给str; 下面是一段演示程序: public class StringDemo{ public static void main(String agrs[]){ Str

转- 关于时间,日期,星期,月份的算法(Java中Calendar的使用方法)

package cn.outofmemory.codes.Date; import java.util.Calendar; import java.util.Date; public class CalendarDemo { public static void main(String[] args) { Calendar calendar=Calendar.getInstance(); calendar.setTime(new Date()); System.out.println("现在时间

Java中循环声明变量方法

Java循环声明变量 之前想这样做,但是网上一直搜索不到,下面是我的方式 项目中 // 得到需要查询外表的数量,然后分别创建缓存,插入数据多的时候如果编码在缓存里面,就不需要再去查询数据库了.key:code/value:pk // 根据"数据来源"有多少非空的 就创建几个,使用 "数据来源字段"+Cache 当cacheMap的key Map<String, Map<String, String>> cacheMap =new HashMa

关于时间,日期,星期,月份的算法(Java中Calendar的使用方法)(一)

package cn.outofmemory.codes.Date; import java.util.Calendar; import java.util.Date; public class CalendarDemo { public static void main(String[] args) { Calendar calendar=Calendar.getInstance(); calendar.setTime(new Date()); System.out.println("现在时间

java中字符串切割的方法总结

StringTokenizer最快 ,基本已经不用了,除非在某些需要效率的场合.Scanner最慢. String和Pattern速度差不多.Pattern稍快些. String和Pattern的split 方法效率相当,常用 public   static   void  main(String [] args){ long  start = System.currentTimeMillis(); for ( int  i= 0 ;i< 100000 ;i++){ test1(); } lon

Java中StringBuffer类append方法的使用

Java中StringBuffer类append方法的使用 append方法的作用是在一个StringBuffer对象后面追加字符串. 例如StringBuffer s = new StringBuffer("Hello");s.append("World");则s的内容是HelloWorld