Linklist与ArrayDeque效率分析

本文对比分析了LinkedList与ArrayDeque两种数据结构在增删改查操作上的性能表现,通过具体测试代码展示了不同操作下的时间消耗,并从源码层面探讨了它们各自的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

下面给出测试代码

public class Linklist_ArrayDeque {

	// LinkedList<E>

	/**
	 * 
	 * @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<String> ade = new ArrayDeque<String>();

		LinkedList<String> linl = new LinkedList<String>();

		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<String> ade = new ArrayDeque<String>();

		LinkedList<String> linl = new LinkedList<String>();

		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的差距是不定的(涉及到方法内部是否存在遍历与创建对象开辟空间的时间花费)

   通过查看LinkedListArrayDeque类可以分析出原因。

       LinkedList Entry对象为节点,实现了双向链表;而ArrayDeque以数组形式实现的队列。对于增删改查这些基本操作LinkedList用到的核心方法为

   private Entry<E> addBefore(Ee, Entry<E> entry) { 
		Entry<E> newEntry = new Entry<E>(e,entry, entry.previous);
		newEntry.previous.next= newEntry;
		newEntry.next.previous= newEntry;
		size++;
		modCount++;
		return newEntry;
   }
		//删除节点
   private E remove(Entry<E>e) {
		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<E>的主要优势

   ListIterator.add(E element) O(1) <---LinkedList<E>的主要优势

  (以上几个常用方法的时间复杂度来自于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方法就可以插入新的节点,没有遍历节点,所有语句只执行一次,所以其时间复杂度为O1;add(int index, E element)remove(intindex)add(Ee)方法的区别是他需要进行链表遍历找到对应位置的几点,然后进行插入、删除操作所以时间复杂度为O(n)Iterator.remove() ListIterator.add(Eelement)方法都是以内部类ListItr为基础,观察ListItr可以发现他内部有一个next属性,所有操作都是针对next的进行的,在执行插入和删除操作是不存在遍历节点的问题,所以时间复杂度为O(1)

   ArrayDeque增删改查涉及到的方法为一下几个

<p>
 public voidaddFirst(E e) {
  if (e == null)
   throw newNullPointerException();
  elements[head = (head - 1) & (elements.length - 1)] = e;
  if (head == tail)
   doubleCapacity();
 }</p><p> public void addLast(E e) {
  if (e == null)
   throw newNullPointerException();
  elements[tail] = e;
  if ((tail = (tail + 1) & (elements.length - 1)) == head)
   doubleCapacity();
 }</p><p> 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;
  }</p><p> public EpollLast() {
  int t =(tail - 1) & (elements.length -1);
  E result = elements[t];
  if(result == null)
  return null;
  elements[t] =null;
  tail = t;
  returnresult;
  }</p><p> public E peekFirst() {
  return elements[head]; // elements[head] is null if deque empty
 }</p><p> public EpeekLast() {
  return elements[(tail - 1) & (elements.length - 1)];
 }</p><p> 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 <tail mod circularity
  if (front >=((t - h) & mask))
  throw new ConcurrentModificationException();
  // Optimize for least elementmotion
  if (front <back) {
  if (h<= i) {
  System.arraycopy(elements, h, elements, h + 1,front);
  } else { //Wrap around
  System.arraycopy(elements, 0, elements, 1, i);
  elements[0] = elements[mask];
  System.arraycopy(elements, h, elements, h + 1, mask -h);
  }
  elements[h] = null;
  head = (h+ 1) & mask;
  return false;
  } else {
  if (i< t) { // Copy the null tail as well
  System.arraycopy(elements, i + 1, elements, i, back);
  tail = t - 1;
  } else { //Wrap around
  System.arraycopy(elements, i + 1, elements, i, mask -i);
  elements[mask] = elements[0];
  System.arraycopy(elements, 1, elements, 0, t);
  tail = (t - 1) &mask;
  }
  return true;
  }
}</p>

   由于ArrayDeque的数据结构为数组类型,所以可以直接定位元素位置,不需要遍历整个队列或链表就可以进行操作,所以这些核心方法其时间复杂度都为O(1),再看api中比较常用的方法,如offerFirst(E e)offerLast(Ee)removeFirst()等方法(即没有遍历操作队列,获取对象位置的方法),其时间复杂度都应该为O1);而对于removeFirstOccurrence(Object o)contains(Object o)remove(Objecto)等方法,由于要遍历队列节点以确定传入对象对应节点的位置,所以其平均时间复杂度应该为O((n+1/2),由于方法中遍历是从队列都开始的,所以正序删除速度要远超于倒序删除的原因,正序删除时间复杂度为O1)而逆序删除的时候时间复杂度为On)。 

    综上:LinkedListArrayDeque实现链表队列的方法不同,一个使用节点对象的方式,一个使用数组的方式。对于这两种链表队列来说,如果只对链表队列的头尾元素进行add/remove的操作那么他们性能相差不多(对于LinkedList来说也包括ListIterator的方法,如果需要遍历操作LinkedList尽量通过ListIterator的方法实现),但是如果需要对链表队列中间的元素进行操作的话,用ArrayDeque更为合适(这是针对不需要遍历链表或节点的操作方法而言)LinkedList的增删改查的时间复杂度(排除极限情况)基本上需要遍历(n+1)/2个节点,ArrayDeque由于底层时数组实现,所以他的的增删改查可以根据索引值,经过一次计算直接获取节点位置,对节点进行操作。

     1.对于ArrayDeque中的方法时间复杂度基本都为O(1),对于LinkedList队列方法,其时间复杂度也基本为O(1)(这里所说的队列方法不包括removeFirstOccurrence(Objecto)需要有遍历操作的方法)

     2. ArrayDequeLinkedList都没有进行同步处理,所以不支持多线程,且ArrayDeque元素不能为NULL

     3.就上面的实验结果来看,对于节点元素数量较少时ArrayDequeLinkedList在同等操作时ArrayDeque略优于LinkedList

     通过学习,了解了LinkedList与ArrayDeque的实现原理以及部分方法的实现,但是仍有疑问存在。

      疑问:在时间复杂度相同时为何ArrayDeque的效率会偏高?

            LinkedList与ArrayDeque都存在遍历的时候,为什么ArrayDeque执行时间要小?

            LinkedList遍历效率是否低于ArrayDeque?为什么?

            LinkedListremove(Object o)的效率为什么低于ArrayDequeremove(Object o)(ArrayDeque删除元素时进行两次数组复制,而LinkedList只进行了节点指针的转换,但实验结果却是ArrayDeque先完成了数据删除。)

      对于上述疑问,希望有大拿帮忙解释一下!

(对于时间复杂度不甚了解,所以本文中时间复杂度没有经过真正的计算只是根据代码与曾经的了解,得出的一个大致范围,如有不对请指正!)

 


 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值