Java数据结构(6)-ArrayDeque

前两篇讲过,ArrayList使用数组性能很高,LinkedList更擅长处理头部数据。而ArrayDeque即使用数组,又擅长处理头部数据。相关特点前面章节已经详细介绍过,所以,要理解ArrayDeque,请先理解前几篇文章的内容。

双端队列

上一篇讲过,LinkedList用链表实现了Deque,即双端队列。ArrayDeque则用数组实现了Deque,Deque用法参考第5篇结尾。现在eclipse中调试以下代码

		ArrayDeque deque = new ArrayDeque();  
		System.out.println("断点1");
		deque.add(1);
		System.out.println("断点2");
		for(int i=2;i<14;i++){
			deque.add(i);
		}
		System.out.println("断点3");
		deque.addFirst(0);
		System.out.println("断点4");
		deque.addFirst(-1);
		System.out.println("断点5");
		deque.addFirst(-2);
		System.out.println("断点6");
  • 断点1:和ArrayList一样,它用一个数组参数element来存储数据,默认初始容量是16。也和LinkedList一样,用两个参数来记录头部和尾部,即head和tail,初始都是0,此时没有添加数据。

  • 断点2:添加第一个数据,和ArrayList一样默认在尾部添加。此时头部head仍然是0,而尾部tail变成了1,即下一次的添加位置。


  • 断点3:此时共添加了13条数据,tail指向坐标13,head仍然是0。


  • 断点4:重点来了,现在不再使用add()从尾部添加,而是用addFirst(0)从头部添加。注意:head头部变成了15,新添加的数据存储到了这里。此时队列头部第一个数据是位置15,第二个数据是位置0,第三个是位置1,有点像循环队列。这样就无需像ArrayList那样移动之前的数据了,从而实现了数组高效的从头部添加数据。

  • 断点5:继续从头部添加,添加到了倒数第二的位置,同上面的原理一样,之前的数据完全不需要移动。

  • 断点6:仍然从头部添加,显然会添加到倒数第三的位置,此时整个数组被填满。容量从16扩容为32,即按2倍容量扩容。注意:扩容时会进行数据整理,最后从头部添加的三个数据本来是在数组结尾,现在移动到了头部,成了真正的头部,如下图,head和tail也相应变成0和16。


先删后增

上面解释了,从头部添加,实际上会添加到数组结尾。但并不是任何时候都是这样。调试以下代码
		ArrayDeque deque = new ArrayDeque();  
		deque.add(1);
		deque.add(2);
		deque.add(3);
		deque.add(4);
		deque.add(5);
		deque.poll();
		deque.poll();
		System.out.println("断点1");
		deque.addFirst(0);
		System.out.println("断点2");
  • 断点1:先添加5条数据,然后使用poll方法从头部取出两条数据,也就是删除。删除其实就是赋null值。

  • 断点2:此时再执行addFirst()从头部添加,不会再添加到结尾,而是添加到了之前头部的前面。因此,无论怎么添加和删除,队列数据始终都是连续的。


数组容量

前面说过,ArrayDeque的数组默认长度是16。也可以像ArrayList那样,在初始时设置一个参数,直接分配较大的容量,以解决反复扩容的性能开销。我现在随便设置一个数字,如19。如下

ArrayDeque deque = new ArrayDeque(19);  

通过调试发现,此时数组elements的长度并不是19,而是32。因为其长度有一定限制标准,最小是8,只能按8的2倍长度扩容,所以其长度只能是8、16、32、64、128等。当设置参数为19时,它的长度会是其中一个刚刚大于19的数字,即32。因为这样的数字适合进行一些二进制运算,ArrayDeque正是通过相关的二进制算法来进行定位,以及确定何时扩容、何时循环链表等。这里不多讨论,知道即可。

ArrayDeque特性和场景

  • LinkedList一样,记录了头和尾的位置,擅长处理头部和尾部的数据,适用于栈和队列。但是并没有提供查询和处理其他位置数据的方法,不像ArrayList可以适应通用场景。
  • 由于使用数组,而且不需要移动位置,性能肯定高于LinkedList。上一篇已经解释过原因。另外,jdk官方文档上也明确指出,当用于队列时,ArrayDeque比LinkedList更快。
  • 当删除大量数据时,数组容量不会缩小,删除的位置仍然会赋null值。如果之后又没有足够的数据添加进来,大量的null会让费大量内存。而LinkedList每个结点被删除后,都会单独被内存回收,不会一直占用内存。
  • 数组初始长度为16,最小只能指定为8,原因上面已经说了。假如数组中只有一个元素,仍然会占用8个内存位置。如果要创建大量这样的数组,同样非常让费内存。ArrayList可以指定任意长度,不存在这样的问题。LinkedList每个结点是独立创建的,不会占用多余空间,也不存在这样的问题。

猜你喜欢

转载自blog.csdn.net/wangb_java/article/details/79543082