您的位置:首页 > 产品设计 > UI/UE

Java进阶--ArrayDeque双端队列完全解析

2017-05-09 20:27 387 查看

ArrayDeque的基本用法

public class DequeDemo {

public static void main(String[] args) {
int []arr={1,2,3,4,5,6,7,8};
ArrayDeque<Integer> aDeque=new ArrayDeque<Integer>();
for(int i=0;i<arr.length;i++)
{
if((i&1)==0)
//如果i是偶数从队头加入,否则从队尾加入
aDeque.addFirst(arr[i]);
else
aDeque.addLast(arr[i]);

}
while(!aDeque.isEmpty())
{
aDeque.pollFirst();
aDeque.pollLast();//此处不严谨但是由于arr的元素恰好是偶数个,本例中也不会出现问题。
}

}

}


上面这段程序简单的演示了如果对双端队列的操作,下面给出上面代码的示意图:



上图是在执行完往双端队列加元素后,双端队列中元素的存储情况,由上图可以得出以下几点结论:

ArrayDeque
默认分配16个空间的数组。数组下标从0-15。


addFirst(arr[i])
操作是将元素从数组的最后一个位置向前依次存放。


addLast(arr[i])
操作是将元素从数组的第一个位置依次向后存放。






上面的图是分别在执行一次
pollFirst()
pollLast()
后双端队列中的存储情况。由上图可知一下几点:

①如果单独将
addFirst(arr[i])
pollFirst()
结合使用的话,先进的元素会后出队,后进的元素反而先队,实现的是栈的功能。


②如果单独将
addLast(arr[i])
pollLast()
结合使用的话,先进的元素会后出队,后进的元素反而先队,实现的也是栈的功能。


再看下面的代码:

public class DequeDemo {

public static void main(String[] args) {
int []arr={1,2,3,4,5,6,7,8};
ArrayDeque<Integer> aDeque=new ArrayDeque<Integer>();
//从双端队列的头部加元素
for(int i=0;i<arr.length;i++)
aDeque.addFirst(arr[i]);
//从双端队列的尾部删元素
while(!aDeque.isEmpty())
aDeque.pollLast();
}

}


上面代码中依次从队头加入元素,然后在队尾删除元素下面是示意图:



使用
addFirst(arr[i])
将元素全部加到双端队列中。



进行一次
pollLast()
后队列中元素的情况。



进行3次
pollLast()
后队列中元素的情况。

由上可以得出以下几点:

①如果单独将
addFirst(arr[i])
pollLast()
结合使用的话,先进的元素会先出队,后进的元素会后出队,实现的是普通队列。


②如果单独将
addLast(arr[i])
pollFirst()
结合使用的话,先进的元素会先出队,后进的元素会后出队,实现的也是普通队列。


深入了解ArrayDeque

下面从源码来看看ArrayDeque是如何实现上述功能的。

构造函数

默认构造函数新建了一个大小为16的数组。

public ArrayDeque() {
elements = (E[]) new Object[16];
//默认构造函数新建了一个大小为16的数组。
}


用户初始化大小的构造函数。

public ArrayDeque(int numElements) {
allocateElements(numElements);
}


上面的构造函数中只用到 allocateElements(numElements) 下面看看allocateElements(numElements) 的源码

private void allocateElements(int numElements) {
int initialCapacity = 8;

if (numElements >= initialCapacity) {
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>>  1);
initialCapacity |= (initialCapacity >>>  2);
initialCapacity |= (initialCapacity >>>  4);
initialCapacity |= (initialCapacity >>>  8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;

if (initialCapacity < 0)
initialCapacity >>>= 1;// 最大分配2 ^ 30 空间
}
elements = (E[]) new Object[initialCapacity];
}


上面的这段代码做一下几点说明:

①当用户要求分配的数组大小小于8时,分配8个空间,当用户要求分配的数组大小为8时分配16个空间。

②当用户要求分配的数组大小大于8时则分配大于用户需要内存的2的最小幂,比如用户需要分配的数组大小为21时,系统会分配大小为32的数组。

③最大分配2 ^ 30 大小的数组。

传入容器的构造函数

public ArrayDeque(Collection<? extends E> c) {
allocateElements(c.size());
//分配数组
addAll(c);
// 将元素加入到 ArrayDeque中
}


下面看看 addAll(c) 的代码:

public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
//将元素全部加到ArrayDeque中。其中add 方法调用了addLast 后面会对addLast源码进行分析
}

public boolean add(E e) {
addLast(e);
return true;
}


下面利用
ArrayDeque(Collection<? extends E> c)
这个构造器来实现一个简单的例子:

public class DequeDemo {
public static void main(String[] args) {
Integer []arr={1,2,3,4,5,6,7,8};
List<Integer> list=Arrays.asList(arr);
//初始化的时候传入一个List的容器。
ArrayDeque<Integer> aDeque=new ArrayDeque<Integer>(list);

while(!aDeque.isEmpty())
System.out.print(aDeque.pollLast()+" ");

}

}


上面代码中初始化时传入一个List的容器,下面示意图看看构造后的ArrayDeque是什么样的。



由上图可知:将list的元素加入到ArrayDeque中的时候是调用了
addLast
方法,
addList
方法是按顺序从前往后加入的。

addFirst

public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
//如果加入的元素为null 抛出空指针异常。
elements[head = (head - 1) & (elements.length - 1)] = e;
//head值先减1,然后将元素放到head位置
if (head == tail)
doubleCapacity();
//如果head == tail 将数组大小扩大一倍
}


上面的tail 和 head到底是啥,还是画图来看看。



由上图可知

①addFirst 加一个元素时,head都会先减1,然后再放元素。head初始值为 0。

② (head - 1) & (elements.length - 1) 使head的值在【0, elements.length - 1】范围之内,同时可以循环利用数组里面的空间,head一直围着数组转圈。

③如果tail==head 的时候,由上图可知数组中的元素已经满了,所以会将数组的扩大一倍。初始时tail=0。

addLast

public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
//将e的值放到tail位置,然后tail+1,如果数组满了扩容一倍。
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}


由上面的图可知 addLast 时操作 tail 在tail位置加入元素,如果数组已经满了就扩容一倍。

pollFirst

public E pollFirst() {
int h = head;
E result = elements[h]; //将elements[h]赋值给result。
if (result == null)
return null;//如果result 为null 直接返回
elements[h] = null;     // 如果result不为null 将elements[h] 赋值为null
head = (h + 1) & (elements.length - 1);// head+1.
return result;
}


下面是一个使用
pollFirst()
的例子。

public class DequeDemo {
public static void main(String[] args) {
Integer []arr={1,2,3,4,5,6,7,8};
List<Integer> list=Arrays.asList(arr);
ArrayDeque<Integer> aDeque=new ArrayDeque<Integer>(list);
for(int i=0;i<3;i++)
aDeque.addFirst(arr[i]);
//往双端队列中添加元素
while(!aDeque.isEmpty())
aDeque.pollFirst();
//使用pollFirst()删除。
}

}


下面看看上面代码的流程图:



添加完元素后的双端队列。



执行一次
pollFirst()
后。



执行两次
pollFirst()
之后



执行三次
pollFirst()
之后



执行6次
pollFirst()
之后。

通过上面的图就很好理解
pollFirst()
的删除流程了。

pollLast

public E pollLast() {
int t = (tail - 1) & (elements.length - 1);
E result = elements[t];
//tail 先减1然后获取tail中的内容。
if (result == null)
return null;//如果result 为null 直接返回
elements[t] = null;//否则将t位置的元素清空再返回result
tail = t;
return result;
}


还是利用上面的那段代码只不过将
pollFirst()
改为
pollLast()
代码如下

public class DequeDemo {
public static void main(String[] args) {
Integer []arr={1,2,3,4,5,6,7,8};
List<Integer> list=Arrays.asList(arr);
ArrayDeque<Integer> aDeque=new ArrayDeque<Integer>(list);
for(int i=0;i<3;i++)
aDeque.addFirst(arr[i]);
//添加元素
while(!aDeque.isEmpty())
aDeque.pollLast();
//删除元素
}

}


依然上图:



添加完元素的队列。



执行一次
pollLast()
后。



执行8次
pollLast()
后。



执行9次
pollLast()
后。

由上面可以总结出:

①每次
pollLast()
前tail先减1,然后再删除,tail指向的位置在元素的上一个位置。


pollLast()
也是绕着数组循环删除的。tail一直绕着数组循环转动。


removeFirst()

public E removeFirst() {
E x = pollFirst();
//调用pollFirst()
if (x == null)
throw new NoSuchElementException();
//当pollFirst()返回null抛出null 元素异常。
return x;
}


由上面代码可知其实
removeFirst()
就是调用
pollFirst()
不同之处在于在当
pollFirst()
返回null
removeFirst()
抛出 null 元素异常。

removeLast()

public E removeLast() {
E x = pollLast();
//调用 pollLast() 当pollLast()返回null抛出null 元素异常。
if (x == null)
throw new NoSuchElementException();
return x;
}


由上面代码可知其实
removeLast()
就是调用
pollLast()
不同之处在于在当
pollLast()
返回null
removeLast()
抛出 null 元素异常。

add(E e)

public boolean add(E e) {
//调用 addLast(e); 添加成功返回 true。
addLast(e);
return true;
}


remove()

public E remove() {
return removeFirst();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息