下面给出测试代码
public class Linklist_ArrayDeque {
// LinkedList
/**
*
* @paramargs
*/
public static void main(String[] args) {
// TODO Auto-generatedmethod stub
getAddTime();
// getRemove();
getRemove(100000, 99999);
}
public static void getRemove(int count, int end) {
ArrayDeque ade = new ArrayDeque();
LinkedList linl = new LinkedList();
for (int i = 0; i < count; i++) {
linl.add(i + "");
}
Long linl_start = new Date().getTime();
for (int i = end; i > 0; i--) {
linl.remove(i + "");
}
Long linl_end = new Date().getTime();
System.out.println("linlOBJ倒序"+count+"删除元素耗时:" + (linl_end - linl_start));
for (int i = 0; i < count; i++) {
linl.add(i + "");
}
Long linl_start_int = new Date().getTime();
for (int i = end; i > 0; i--) {
linl.remove(i);
}
Long linl_end_int = new Date().getTime();
System.out
.println("linlINT倒序"+count+"删除元素耗时:" + (linl_end_int - linl_start_int));
for (int i = 0; i < count; i++) {
ade.add(i + "");
}
Long ade_start = new Date().getTime();
for (int i = end; i > 0; i--) {
ade.remove(i + "");
}
Long ade_end = new Date().getTime();
System.out.println("ade倒序"+count+"删除素耗时:" + (ade_end - ade_start));
for (int i = 0; i < count; i++) {
ade.add(i + "");
}
Long ade_start_z = new Date().getTime();
for (int i = 0; i < end; i++) {
ade.remove(i + "");
}
Long ade_end_z = new Date().getTime();
System.out.println("ade正序"+count+"删除元素耗时:" + (ade_end_z - ade_start_z));
}
public static void getAddTime() {
ArrayDeque ade = new ArrayDeque();
LinkedList linl = new LinkedList();
Long linl_start = new Date().getTime();
for (int i = 0; i < 100000; i++) {
linl.add(i + "");
}
Long linl_end = new Date().getTime();
System.out.println("linl插入10W元素耗时:" + (linl_end - linl_start));
Long ade_start = new Date().getTime();
for (int i = 0; i < 100000; i++) {
ade.add(i + "");
}
Long ade_end = new Date().getTime();
System.out.println("ade插入10W元素耗时:" + (ade_end - ade_start));
}
}
linl插入10W元素耗时:36
ade插入10W元素耗时:26
linlOBJ倒序100000删除元素耗时:57852
linlINT倒序100000删除元素耗时:4
ade倒序100000删除素耗时:47907
ade正序100000删除元素耗时:22
通过运行结果可以看出LinkedList在插入节点没有ArrayDeque速度快,但是LinkedList的删除节点的速度是不同的方法耗费时间与ArrayDeque的差距是不定的(涉及到方法内部是否存在遍历与创建对象开辟空间的时间花费)。
通过查看LinkedList与ArrayDeque类可以分析出原因。
LinkedList以 Entry对象为节点,实现了双向链表;而ArrayDeque以数组形式实现的队列。对于增删改查这些基本操作LinkedList用到的核心方法为
private Entry addBefore(Ee, Entry entry) {
Entry newEntry = new Entry(e,entry, entry.previous);
newEntry.previous.next= newEntry;
newEntry.next.previous= newEntry;
size++;
modCount++;
return newEntry;
}
//删除节点
private E remove(Entrye) {
if (e == header)
throw new NoSuchElementException();
E result = e.element;
e.previous.next= e.next;
e.next.previous= e.previous;
e.next = e.previous=null;
e.element = null;
size--;
modCount++;
return result;
}
我们可以看出对于确定了元素插入位置的删除与增加操作时间复杂度都是O(1)(如果错误请大家指正),所以对于LinkedList的几个主要方法的时间复杂度来说,他们的时间复杂度主要是由于查询节点所用时间决定的。
get(int index) O(n)
add(E element) O(1)
add(int index, E element) O(n)
remove(int index) O(n)
Iterator.remove() O(1) <--- LinkedList
ListIterator.add(E element) O(1) <---LinkedList
(以上几个常用方法的时间复杂度来自于http://bookshadow.com/weblog/2014/11/23/java-linkedlist-vs-arraylist/)
通过源码也可以分析出来,在get(int index)方法中调用entry(intindex)的方法,在entry(intindex)方法中,需要遍历链表获取位置节点(虽然在方法中做了处理,最多遍历1/2的长度,但是其数量级并没有改变);add(E e)此方法讲元素e直接插入链表末尾,由于链表的标记节点的存在head,可以直接获取到最后一个节点的位置,之后调用addBefore方法就可以插入新的节点,没有遍历节点,所有语句只执行一次,所以其时间复杂度为O(1);add(int index, E element)、remove(intindex)与add(Ee)方法的区别是他需要进行链表遍历找到对应位置的几点,然后进行插入、删除操作所以时间复杂度为O(n);Iterator.remove() 与ListIterator.add(Eelement)方法都是以内部类ListItr为基础,观察ListItr可以发现他内部有一个next属性,所有操作都是针对next的进行的,在执行插入和删除操作是不存在遍历节点的问题,所以时间复杂度为O(1)。
ArrayDeque增删改查涉及到的方法为一下几个
public voidaddFirst(E e) {
if (e == null)
throw newNullPointerException();
elements[head = (head - 1) & (elements.length - 1)] = e;
if (head == tail)
doubleCapacity();
}
public void addLast(E e) {
if (e == null)
throw newNullPointerException();
elements[tail] = e;
if ((tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
public E pollFirst() {
int h = head;
E result = elements[h]; //Element is null if deque empty
if(result == null)
return null;
elements[h] =null; // Must null out slot
head = (h+ 1) & (elements.length -1);
returnresult;
}
public EpollLast() {
int t =(tail - 1) & (elements.length -1);
E result = elements[t];
if(result == null)
return null;
elements[t] =null;
tail = t;
returnresult;
}
public E peekFirst() {
return elements[head]; // elements[head] is null if deque empty
}
public EpeekLast() {
return elements[(tail - 1) & (elements.length - 1)];
}
private booleandelete(int i) {
checkInvariants();
final E[]elements = this.elements;
final int mask= elements.length - 1;
final int h = head;
final int t = tail;
final intfront = (i - h) & mask;
final intback = (t - i) & mask;
// Invariant: head <= i =((t - h) & mask))
throw new ConcurrentModificationException();
// Optimize for least elementmotion
if (front
由于ArrayDeque的数据结构为数组类型,所以可以直接定位元素位置,不需要遍历整个队列或链表就可以进行操作,所以这些核心方法其时间复杂度都为O(1),再看api中比较常用的方法,如offerFirst(E e),offerLast(Ee),removeFirst()等方法(即没有遍历操作队列,获取对象位置的方法),其时间复杂度都应该为O(1);而对于removeFirstOccurrence(Object o)、contains(Object o)、remove(Objecto)等方法,由于要遍历队列节点以确定传入对象对应节点的位置,所以其平均时间复杂度应该为O((n+1)/2),由于方法中遍历是从队列都开始的,所以正序删除速度要远超于倒序删除的原因,正序删除时间复杂度为O(1)而逆序删除的时候时间复杂度为O(n)。
综上:LinkedList与ArrayDeque实现链表队列的方法不同,一个使用节点对象的方式,一个使用数组的方式。对于这两种链表队列来说,如果只对链表队列的头尾元素进行add/remove的操作那么他们性能相差不多(对于LinkedList来说也包括ListIterator的方法,如果需要遍历操作LinkedList尽量通过ListIterator的方法实现),但是如果需要对链表队列中间的元素进行操作的话,用ArrayDeque更为合适(这是针对不需要遍历链表或节点的操作方法而言)。LinkedList的增删改查的时间复杂度(排除极限情况)基本上需要遍历(n+1)/2个节点,ArrayDeque由于底层时数组实现,所以他的的增删改查可以根据索引值,经过一次计算直接获取节点位置,对节点进行操作。
1.对于ArrayDeque中的方法时间复杂度基本都为O(1),对于LinkedList中队列方法,其时间复杂度也基本为O(1)。(这里所说的队列方法不包括removeFirstOccurrence(Objecto)需要有遍历操作的方法)
2. ArrayDeque与LinkedList都没有进行同步处理,所以不支持多线程,且ArrayDeque元素不能为NULL。
3.就上面的实验结果来看,对于节点元素数量较少时ArrayDeque与LinkedList在同等操作时ArrayDeque略优于LinkedList。
通过学习,了解了LinkedList与ArrayDeque的实现原理以及部分方法的实现,但是仍有疑问存在。
疑问:在时间复杂度相同时为何ArrayDeque的效率会偏高?
LinkedList与ArrayDeque都存在遍历的时候,为什么ArrayDeque执行时间要小?
LinkedList遍历效率是否低于ArrayDeque?为什么?
LinkedList的remove(Object o)的效率为什么低于ArrayDeque的remove(Object o)?(ArrayDeque删除元素时进行两次数组复制,而LinkedList只进行了节点指针的转换,但实验结果却是ArrayDeque先完成了数据删除。)
对于上述疑问,希望有大拿帮忙解释一下!
(对于时间复杂度不甚了解,所以本文中时间复杂度没有经过真正的计算只是根据代码与曾经的了解,得出的一个大致范围,如有不对请指正!)