ArrayDeque源码解析

1.ArrayDeque解析

1.1继承体系

由其名字可以看出,其是一个由数组实现的双端队列,对比LinkedList是由链表实现的双端队列,ArrayDeque实现了Queue接口,也是Collection下的一个常用类。

在这里插入图片描述

1.2常用方法

public interface Deque<E> extends Queue<E> {
    
    // 添加元素到队列头
    void addFirst(E e);
    
    // 添加元素到队列尾
    void addLast(E e);
    
    // 添加元素到队列头
    boolean offerFirst(E e);
    
    // 添加元素到队列尾
    boolean offerLast(E e);
    
    // 从队列头移除元素
    E removeFirst();
    
    // 从队列尾移除元素
    E removeLast();
    
    // 从队列头移除元素
    E pollFirst();
    
    // 从队列尾移除元素
    E pollLast();
    
    // 查看队列头元素
    E getFirst();
    
    // 查看队列尾元素
    E getLast();
    
    // 查看队列头元素
    E peekFirst();
    
    // 查看队列尾元素
    E peekLast();
    
    // 从队列头向后遍历移除指定元素
    boolean removeFirstOccurrence(Object o);
    // 从队列尾向前遍历移除指定元素
    boolean removeLastOccurrence(Object o);

//        *** 队列中的方法 ***
    
    // 添加元素,等于addLast(e)
    boolean add(E e);
     // 添加元素,等于offerLast(e)
    
    boolean offer(E e);
    // 移除元素,等于removeFirst()
    E remove();
    
    // 移除元素,等于pollFirst()
    E poll();
    
    // 查看元素,等于getFirst()
    E element();
    
    // 查看元素,等于peekFirst()
    E peek();

    // *** 栈方法 ***
    // 入栈,等于addFirst(e)
    void push(E e);
    // 出栈,等于removeFirst()
    E pop();

//           *** Collection中的方法 ***                      
    
    // 删除指定元素,等于removeFirstOccurrence(o)
    boolean remove(Object o);
    // 检查是否包含某个元素
    boolean contains(Object o);
    // 元素个数
    public int size();
    // 迭代器
    Iterator<E> iterator();
    // 反向迭代器
    Iterator<E> descendingIterator();
}

2.核心结构

2.1属性

    //底层的数组
	transient Object[] elements; 
	
	//头指针
    transient int head;
	
	//尾指针
    transient int tail;
	
	//默认的最小容量,注意:elements的长度一定是2的次方幂。
    private static final int MIN_INITIAL_CAPACITY = 8;

2.2构造器

  • 调用无参构造器时,默认创建一个长度为16的数组。
  • 调用传入初始容量n的构造器,当n小于8时,会初始化一个长度为8的一个数组。当n大于等于8时,会初始化一个长度为<大于n的最小的2的幂>的数组 (比如传入3算出来是8 传入9算出来是16 传入16算出来是32)
    /*          1
     * 空参构造器,底层初始化一个长度为16的数组
     */
	public ArrayDeque() {
        elements = new Object[16];
    }

	/*          2
	 * 传入初始容量,注意最终的容量是大于(没有等于)numElements的最大的2的幂
	 * 然后会创建出来。
	 */
    public ArrayDeque(int numElements) {
        allocateElements(numElements);
    }
	
	/*          3
	 *  创建一个长度为<小于等于c.size的最大的2的幂>的数组
	 *  然后将c中的元素添加到elements中。
	 */
    public ArrayDeque(Collection<? extends E> c) {
        allocateElements(c.size());
        /
        addAll(c);
    }

allocateElements

 	//构造一个长度为<严格大于numElements的最小的2的幂>的一个数组
	private void allocateElements(int numElements) {
        elements = new Object[calculateSize(numElements)];
   }
		
		
	//返回严格大于numElements的最小的2的幂 (当numElements小于8时,返回8)
    private static int calculateSize(int numElements) {
        //MIN_INITIAL_CAPACITY = 8
        int initialCapacity = MIN_INITIAL_CAPACITY;
        //当numElements大于等于8时,计算出大于numElements的最小的2的幂
        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
        }
        //这里如果numElements小于8时,直接返回8.
        return initialCapacity;
    }

3.核心方法详解

3.1入队

  • 入队有两种方式,从队列头或者从队列尾;
  • 如果容量不够了,直接扩大为两倍;
  • 通过取模的方式让头尾指针在数组范围内循环;
  • x & (len - 1) = x % len,使用&的方式更快;
    //从队头入队
	public void addFirst(E e) {
        //元素不允许为NULL
        if (e == null)
            throw new NullPointerException();
        /*
         *  因为element.length一定是2的幂,2的幂-1的二进制从低位起是一串1,高位都是0
         *  初始时head = 0, 0 - 1 = - 1 ,-1 & 15 = 15, 此时head = 15
         *      下一次 15 - 1 = 14, 14 & 15 = 14 此时head = 14
         *      在下一次 14 - 1 = 13, 13 & 15 = 13,此时head = 13
         *       ......
         *      最终当数组未满时,会循环回来。
         *      即head指向的是当前队头元素。
         */
        elements[head = (head - 1) & (elements.length - 1)] = e;
        //tail指向的是头元素的下一个位置。判断head == tail即判断数组是否满了,需要扩容。
        if (head == tail)
            //从方法名可以看出,扩容为原数组长度2倍。
            doubleCapacity();
    }


// 从队尾入队
public void addLast(E e) {
    // 不允许null元素
    if (e == null)
        throw new NullPointerException();
    //初始时tail为0,直接入队,此时tail指向的是从队尾入队队列的头元素的下一个位置。
    elements[tail] = e;
    
	/*
	 * head指向的是队头元素的位置
	 *  tail + 1指向队头的下一个元素,判断是否 == head,即判断数组是否满了。
	 *  即是否走扩容的逻辑。
	 */
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)
        doubleCapacity();
}

3.2扩容

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

元素迁移示意图

在这里插入图片描述

3.3出队

// 从队列头出队
public E pollFirst() {
    int h = head;
    @SuppressWarnings("unchecked")
    // 取队列头元素 (head指向的就是头元素)
    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() {
    // 尾指针左移一位 因为通过addLast()我们可以知道,tail指向的是头元素的下一个位置
    int t = (tail - 1) & (elements.length - 1);
    @SuppressWarnings("unchecked")
    // 取当前尾指针处元素
    E result = (E) elements[t];
    
    // 如果队列为空返回null
    if (result == null)
        return null;
    
    // 将当前尾指针处置为空
    elements[t] = null;
    
    // tail指向新的尾指针处
    tail = t;
    
    // 返回取得的元素
    return result;
}

3.4作为栈使用

//头部入队
public void push(E e) {
    addFirst(e);
}

//头部出队
public E pop() {
    //调用的还是pollFirst()
    return removeFirst();
}

4.总结

(1)ArrayDeque是采用数组方式实现的双端队列;

(2)ArrayDeque的出队入队是通过头尾指针循环利用数组实现的;

(3)ArrayDeque容量不足时是会扩容的,每次扩容容量增加一倍

(4)ArrayDeque可以直接作为栈使用;

双端队列与双重队列

双端队列与双重队列?

  • 双端队列(Deque)是指队列的两端都可以进出元素的队列,里面存储的是实实在在的元素。
  • 双重队列(Dual Queue)是指一种队列有两种用途,里面的节点分为数据节点和非数据节点,它是LinkedTransferQueue使用的数据结构。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shstart7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值