JDK源码阅读—ArrayDeque

简介

双端队列的实现一般有链表和循环数组两种,ArrayDeque就是使用循环数组实现的双端队列,其内部维护着一个数组,并且当数组空间不足时,会自动扩容,容量变为原来的2倍。

以下分析基于corretto-1.8.0_282版本。

继承关系

ArrayDeque继承关系.png

  1. 实现了Cloneable接口,可以通过.clone()方法克隆出一个实例。
  2. 实现了Serializable接口,可以被序列化。
  3. 实现了Deque接口,可以作为双端队列使用。

属性

elements

/**
 * 储存元素的数组,大小始终为2的幂
 * 因为使用循环数组,头尾指针的计算可能需要取模运算,
 * 数组长度定为2的幂就可以改为位运算,速度会快一点
 */
transient Object[] elements;

head

/**
 * 双端队列的头指针
 * 总是指向位于队列头的元素
 */
transient int head;

tail

/**
 * 双端队列的尾指针
 * 总是指向队列尾的下一个待插入的位置
 */
transient int tail;

MIN_INITIAL_CAPACITY

/**
 * 最小初始化容量
 */
private static final int MIN_INITIAL_CAPACITY = 8;

构造方法

ArrayDeque()

/**
 * 使用默认容量(16)实例化ArrayDeque
 */
public ArrayDeque() {
    elements = new Object[16];
}

ArrayDeque(int numElements)

/**
 * 使用给定的容量实例化ArrayDeque,容量会设置为2的幂
 */
public ArrayDeque(int numElements) {
    allocateElements(numElements);
}

/**
 * 根据传入参数申请数组空间,参数会被调整为2的幂
 */
private void allocateElements(int numElements) {
    elements = new Object[calculateSize(numElements)];
}

/**
 * 将传入参数格式化为大于参数的最小的2的幂,同时处理边界情况
 * 逻辑与HashMap中的基本类似,只不过本方法返回的值为大于numElements
 */
private static int calculateSize(int numElements) {

    // 容量不能小于MIN_INITIAL_CAPACITY(8)
    int initialCapacity = MIN_INITIAL_CAPACITY;

    // 将传入参数调整为大于numElements的最小的2的幂
    if (numElements >= initialCapacity) {
        // 此处没有减一操作,所以最后计算的结果一定大于numElements
        initialCapacity = numElements;
        initialCapacity |= (initialCapacity >>>  1);
        initialCapacity |= (initialCapacity >>>  2);
        initialCapacity |= (initialCapacity >>>  4);
        initialCapacity |= (initialCapacity >>>  8);
        initialCapacity |= (initialCapacity >>> 16);
        initialCapacity++;

        // 超过Integer.MAX_VALUE,设置为2的30次方
        if (initialCapacity < 0)   // Too many elements, must back off
            initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
    }
    return initialCapacity;
}

ArrayDeque(Collection<? extends E> c)

/**
 * 根据传入集合实例化ArrayDeque,同时将传入集合的所有元素复制过来
 */
public ArrayDeque(Collection<? extends E> c) {
    // 根据传入集合大小设置容量
    allocateElements(c.size());
    // 复制所有元素
    addAll(c);
}

方法

addFirst(E e)

/**
 * 队列头插入元素
 * 先移动头指针再插入元素
 */
public void addFirst(E e) {
    // 不能插入空元素
    if (e == null)
        throw new NullPointerException();

    // 头指针减一再对数组长度取模,得到插入位置索引,在此所索引处插入元素
    elements[head = (head - 1) & (elements.length - 1)] = e;

    // 若头指针和尾指针重合,说明数组已满,进行扩容
    if (head == tail)
        doubleCapacity();
}

addLast(E e)

/**
 * 队列尾插入元素
 * 先插入元素再移动尾指针
 */
public void addLast(E e) {
    // 不能插入空元素
    if (e == null)
        throw new NullPointerException();

    // 尾指针处插入元素
    elements[tail] = e;

    // 尾指针加一,若尾指针和头指针重合,则需要进行扩容
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)
        doubleCapacity();
}

pollFirst()

/**
 * 移除队列头部元素
 * 先弹出元素,再移动头指针,与插入时相反
 */
public E pollFirst() {
    int h = head;
    @SuppressWarnings("unchecked")
    E result = (E) elements[h];

    // 队列为空时返回null
    if (result == null)
        return null;

    // 将此位置置为null
    elements[h] = null;

    // 移动头指针
    head = (h + 1) & (elements.length - 1);
    return result;
}

pollLast()

/**
 * 移除队列尾部元素
 * 先移动尾指针,再弹出元素,与插入时相反
 */
public E pollLast() {
    // 移动尾指针
    int t = (tail - 1) & (elements.length - 1);
    @SuppressWarnings("unchecked")
    E result = (E) elements[t];

    // 队列为空,返回null
    if (result == null)
        return null;

    // 此位置置为null
    elements[t] = null;
    tail = t;
    return result;
}

getFirst()

/**
 * 获取队列头元素
 */
public E getFirst() {
    // 因为头指针总是指向队列头元素,所以直接返回头指针处的元素
    @SuppressWarnings("unchecked")
    E result = (E) elements[head];
    if (result == null)
        throw new NoSuchElementException();
    return result;
}

getLast()

/**
 * 获取队列尾元素
 */
public E getLast() {
    // 因为尾指针总是指向队列尾元素的下一个待插入的位置,所以需要向左移动一位
    @SuppressWarnings("unchecked")
    E result = (E) elements[(tail - 1) & (elements.length - 1)];
    if (result == null)
        throw new NoSuchElementException();
    return result;
}

removeFirstOccurrence(Object o)

/**
 * 从头向尾查找给定的元素,如果存在,则将其删除
 */
public boolean removeFirstOccurrence(Object o) {
    if (o == null)
        return false;
    int mask = elements.length - 1;

    // 队列头索引
    int i = head;
    Object x;

    // 从队列头向队列尾遍历
    while ( (x = elements[i]) != null) {
        if (o.equals(x)) {

            // 删除之
            delete(i);
            return true;
        }
        i = (i + 1) & mask;
    }
    return false;
}

/**
 * 删除给定索引处的元素
 */
private boolean delete(int i) {
    // 运行时可能不会做检查,取决于虚拟机是否开启断言
    checkInvariants();

    final Object[] elements = this.elements;
    final int mask = elements.length - 1;
    final int h = head;
    final int t = tail;

    // 从i处将数组分为前后两半部分
    final int front = (i - h) & mask;
    final int back  = (t - i) & mask;

    // i需要满足 head <= i < tail的条件,分一下三种情况
    // 1.
    // ----------------
    // h    i      t
    //
    // 2.
    // ----------------
    //     t     h   i
    //
    // 3.
    // ----------------
    //  i  t     h
    if (front >= ((t - h) & mask))
        throw new ConcurrentModificationException();

    // 选择元素较少的一方进行移动
    if (front < back) {
        if (h <= i) {
            // 对应上面图示的1、2两种情况
            // 从队列头h到(i - h)处,所有元素向后移动一位
            System.arraycopy(elements, h, elements, h + 1, front);
        } else {
            // 对应上面图示的情形3
            // 先把从0到(i - 1)的元素向后移动一位,覆盖掉i处的元素,相当于删除了
            System.arraycopy(elements, 0, elements, 1, i);
            // 数组最后一个元素移动到索引0处
            elements[0] = elements[mask];
            // 最后把从h到(length - 2)处的元素都向后移动一位
            System.arraycopy(elements, h, elements, h + 1, mask - h);
        }

        // 原来头指针指向的位置已经空出来了,将其置为null
        elements[h] = null;

        // 头指针向后移动一位
        head = (h + 1) & mask;
        return false;
    } else {
        if (i < t) {
            // 对应上面图示的1、3两种情况
            // 从(i + 1)到(tail - 1)的元素向前移动一位
            System.arraycopy(elements, i + 1, elements, i, back);
            // 移动尾指针
            tail = t - 1;
        } else {
            // 对应上面图示的情形2
            // 先把从(i + 1)到(length - 1 - i)处的元素向前移动一位
            System.arraycopy(elements, i + 1, elements, i, mask - i);
            // 再把索引0处的元素移动到数组末尾
            elements[mask] = elements[0];
            // 最后把从1到tail处的元素向前移动一位
            System.arraycopy(elements, 1, elements, 0, t);
            // 移动尾指针
            tail = (t - 1) & mask;
        }
        return true;
    }
}

/**
 * 一些条件断言
 */
private void checkInvariants() {
    // tail指针指向的位置必须永远是空位
    assert elements[tail] == null;

    // head == tail 说明队列为空,则头指针head指向位置必须为null
    // 否则头指针和尾指针的前一个位置不能为空
    assert head == tail ? elements[head] == null :
        (elements[head] != null &&
            elements[(tail - 1) & (elements.length - 1)] != null);

    // 头指针前一个位置必须为null
    assert elements[(head - 1) & (elements.length - 1)] == null;
}

removeLastOccurrence(Object o)

/**
 * 从尾向头查找给定的元素,如果存在,则将其删除
 */
public boolean removeLastOccurrence(Object o) {
    if (o == null)
        return false;
    int mask = elements.length - 1;

    // 队列尾索引
    int i = (tail - 1) & mask;
    Object x;

    // 从队列尾向队列头遍历
    while ( (x = elements[i]) != null) {
        if (o.equals(x)) {

            // 删除之
            delete(i);
            return true;
        }
        i = (i - 1) & mask;
    }
    return false;
}

size()

/**
 * 获取队列元素个数
 */
public int size() {
    // 当head <= tail时
    // 0              15
    // ----------------
    //    h     t
    // size = t - h   当(h <= t)时
    //
    // 当tail < head时
    // 0              15
    // ----------------
    //    t        h
    // size = (t - 0) + (15 - h + 1)
    //      = t - h + 16       当(t < h)时
    // (t - h + 16) % 16 = t - h
    // size = (t - h) % length
    return (tail - head) & (elements.length - 1);
}

isEmpty()

/**
 * 头尾指针重合,队列为空
 */
public boolean isEmpty() {
    return head == tail;
}

contains(Object o)

/**
 * 判断队列中是否包含给定元素
 */
public boolean contains(Object o) {
    if (o == null)
        return false;
    int mask = elements.length - 1;
    int i = head;
    Object x;
    // 从队列头向队列尾遍历
    while ( (x = elements[i]) != null) {
        if (o.equals(x))
            return true;
        // 移动指针
        i = (i + 1) & mask;
    }
    return false;
}

clear()

/**
 * 清空队列
 */
public void clear() {

    // 缓存头尾节点
    int h = head;
    int t = tail;
    // 队列不为空
    if (h != t) {
        head = tail = 0;
        int i = h;
        int mask = elements.length - 1;

        // 从队列头向队列尾遍历,清空所有元素
        do {
            elements[i] = null;
            i = (i + 1) & mask;
        } while (i != t);
    }
}

总结

  1. ArrayDeque底层数据结构为循环数组,数组长度总是为2的幂,数组填满时会自动扩容,容量变为原来的2倍。
  2. ArrayDeque最小容量为16(大于MIN_INITIAL_CAPACITY(8)的最小的2的幂)。
  3. ArrayDeque可用作队列,双端队列,栈来使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值