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

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();
    }
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值