ArrayDeque 使用方法与源码分析

在这里插入图片描述

ArrayDeque

接口的可调整大小的数组实现Deque。数组双端队列没有容量限制;它们会根据需要增长以支持使用。它们不是线程安全的;在没有外部同步的情况下,它们不支持多线程并发访问。禁止使用空元素。此类可能比 Stack用作堆栈时更快,并且比LinkedList 用作队列时更快。

这个类的iterator方法返回的迭代器是 快速失败的:如果在迭代器创建后的任何时候修改了双端队列,除了通过迭代器自己的remove 方法之外,迭代器通常会抛出一个ConcurrentModificationException. 因此,面对并发修改,迭代器快速而干净地失败,而不是在未来不确定的时间冒任意的、非确定性的行为。


属性

//存储元素的数组
transient Object[] elements; 
//队列头位置
transient int head;
//队列尾位置
transient int tail;
//最小初始容量
private static final int MIN_INITIAL_CAPACITY = 8;

使用数组存储元素,并使用头尾指针标识队列的头和尾,其最小容量是8

构造函数

计算容量,这段代码的逻辑是算出大于numElements的最接近的2的n次方且不小于8

比如,3算出来是8,9算出来是16,33算出来是64

默认初始容量是16,最小容量是8

private void allocateElements(int numElements) {
        int initialCapacity = MIN_INITIAL_CAPACITY;
        // Find the best power of two to hold elements.
        // Tests "<=" because arrays aren't kept full.
        if (numElements >= initialCapacity) {
            initialCapacity = numElements;
            initialCapacity |= (initialCapacity >>>  1);
            initialCapacity |= (initialCapacity >>>  2);
            initialCapacity |= (initialCapacity >>>  4);
            initialCapacity |= (initialCapacity >>>  8);
            initialCapacity |= (initialCapacity >>> 16);
            initialCapacity++;

            if (initialCapacity < 0)   // Too many elements, must back off
                initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
        }
        elements = new Object[initialCapacity];
    }

方法

入队

addFirst()/ offerFirst()方法将一个元素添加到Deque的正面。 同样, *addLast()/ offerLast()*方法将元素添加到末尾。

队头入队

public void addFirst(E e) {
        if (e == null)
            throw new NullPointerException();
        //将head指针减1并与数组长度减1取模
        //目的是防止数组到头了边界溢出
        //如果到头了就从尾部再向前
        //相当于循环利用数组
        //栗子:head为0,数组长度为8.(-1&7)=(-1%8)=7 
        elements[head = (head - 1) & (elements.length - 1)] = e;
        if (head == tail)
            doubleCapacity();//如果头尾在一起了,就触发扩容
    }

队尾入队

public void addLast(E e) {
        if (e == null)
            throw new NullPointerException();
        elements[tail] = e;// //可以看到tail指针指向的是队列的最后一个元素的下一个位置
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)
            doubleCapacity();
    }

整个流程:

  1. 入队有两种方式,从队列头或者从队列尾
  2. 如果容量不够了,直接扩大为两倍
  3. 通过取模的方式让头尾指针在数组范围内循环
  4. x & (len - 1) = x % len,使用&的方式更快

扩容

private void doubleCapacity() {//两倍扩容
        assert head == tail;
        int p = head;//头指针的位置
        int n = elements.length;//旧数组的长度
        int r = n - p; // head右边元素的个数
        int newCapacity = n << 1;//扩容两倍
        if (newCapacity < 0) //判断是否溢出
            throw new IllegalStateException("Sorry, deque too big");
        Object[] a = new Object[newCapacity];
        System.arraycopy(elements, p, a, 0, r);//将旧数组head之后的元素拷贝到新数组中
        System.arraycopy(elements, 0, a, r, p);//将就数组小标0到head之间的元素拷贝到新数组中
        elements = a;//赋值给新数组
        head = 0; //head指向0,tail指向旧数组长度表示的位置
        tail = n;
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sp04qi1H-1652950375638)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220426170635969.png)]

出队

通过remove/pool First/Last进行删除首位元素

队头出队

 public E pollFirst() {
        int h = head;
        @SuppressWarnings("unchecked")
        E result = (E) elements[h];//取队列头元素
         // 如果队列为空,就返回null
        if (result == null)
            return null;
        elements[h] = null;     //将队列头置为空
        head = (h + 1) & (elements.length - 1);//队列头指针想右移动一位
        return result;
    }

队尾出队

 public E pollLast() {
        int t = (tail - 1) & (elements.length - 1);//尾指针左移一位
        @SuppressWarnings("unchecked")
        E result = (E) elements[t];//取当前尾指针处元素
        if (result == null)
            return null;
        elements[t] = null;//将当前尾指针处置为空
        tail = t;//tail指向新的尾指针处
        return result;
    }

总结

  1. 出队有两种方式,从队列头或者从队列尾
  2. 通过取模的方式让头尾指针在数组范围内循环
  3. 出队之后没有缩容

检索

通过peek/peekFirst,peekLast获取头尾值

 public E peekFirst() {
        // 如果队列是空返回null
        return (E) elements[head];
    }

作为栈

Deque可以直接作为栈来使用

import java.util.*;
public class Test {
    public static void main(String[] args) {
        Deque<Integer> stack = new ArrayDeque<>();
        stack.push(1);//进栈
        stack.push(4);
        System.out.println(stack.toString());
        System.out.println(stack.peek());//获取栈顶元素
        stack.pop();//出栈
        System.out.println(stack.toString());
    }


}

总结

  1. ArrayDeque是采用数组方式实现的双端队列
  2. ArrayDeque的出队入队是通过头尾指针循环利用数组实现的
  3. ArrayDeque容量不足时是会扩容的,每次扩容容量增加一倍
  4. ArrayDeque可以直接作为栈使用

猜你喜欢

转载自blog.csdn.net/qq_15604349/article/details/124866798