JDK类库源码分析系列3--集合类分析(6) 集合5-ArrayDeque

178 阅读6分钟
前面是准备将这一篇与Vector&Stack那边放在一起的,但在梳理源码逻辑的时候发现,ArrayDeque这个类写的有些地方还是很有亮点的,所以我们来详细梳理一下。

一、ArrayDeque

​ 这个是用数组的形式对Deque接口的方法实现,类似与上一篇文章用链表的形式对Deque接口的实现,不过其的实现是一个双端队列。

1、结构

public class ArrayDeque<E> extends AbstractCollection<E>
                           implements Deque<E>, Cloneable, Serializable

​ 可以看到其是继承了AbstractCollection类。

2、变量

1)、elements

transient Object[] elements;

​ 存放元素的数组。

2)、head

transient int head;

​ 队首元素的位置。

3)、tail

transient int tail;

​ 队尾元素的位置。

3、构造方法

1)、ArrayDeque()

public ArrayDeque() {
    elements = new Object[16];
}

​ 可以看到其的初始化数组大小是16。

2)、ArrayDeque(int numElements)

public ArrayDeque(int numElements) {
    elements =
        new Object[(numElements < 1) ? 1 :
                   (numElements == Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                   numElements + 1];
}

​ 这个是根据入参numElements来决定大小的,然后小于1,则用1替代。

​ 同样其也有Collection形式的构造方法ArrayDeque(Collection<? extends E> c)。

4、方法

1)、addFirst(E e)

public void addFirst(E e) {
    if (e == null)
        throw new NullPointerException();
    final Object[] es = elements;
    es[head = dec(head, es.length)] = e;
    if (head == tail)
        grow(1);
}

​ 这里就是添加内容到队列的头部,可以看到这里是通过dec方法获取本次的index位置,再将其赋值给head,同时将e赋值到该index位置。添加后如果首尾相接就通过grow去扩容,可以看到这里是额外添加的1。

2)、dec(int i, int modulus)

static final int dec(int i, int modulus) {
    if (--i < 0) i = modulus - 1;
    return i;
}

​ 这里就是双端链表的逻辑了,同时要注意你在往队首添加是以first为标识,但其实first是从后往前移动的,例如这里,最开始是初始化容量modulus-1大小(也就是最后位置的index),因为i最开始是0减1小于0所以i就为modulus-1,之后就一直再往前自减。

3)、grow(int needed)

private void grow(int needed) {
    // overflow-conscious code
    final int oldCapacity = elements.length;
    int newCapacity;
    // Double capacity if small; else grow by 50%
    int jump = (oldCapacity < 64) ? (oldCapacity + 2) : (oldCapacity >> 1);
    if (jump < needed
        || (newCapacity = (oldCapacity + jump)) - MAX_ARRAY_SIZE > 0)
        newCapacity = newCapacity(needed, jump);
    final Object[] es = elements = Arrays.copyOf(elements, newCapacity);
    // Exceptionally, here tail == head needs to be disambiguated
    if (tail < head || (tail == head && es[head] != null)) {
        // wrap around; slide first leg forward to end of array
        int newSpace = newCapacity - oldCapacity;
        System.arraycopy(es, head,
                         es, head + newSpace,
                         oldCapacity - head);
        for (int i = head, to = (head += newSpace); i < to; i++)
            es[i] = null;
    }
}

​ 这里的入参是表明本次扩容需要额外添加的最小参数(要明白这个意思),下面我们来具体看其操作。首先是本次需要额外添加的容量,这里是如果现在还没有到64,计算本次添加的额外容量是原来的容量+2。如果是超过直接右移1位。

​ 然后与needed这个本次需增加的最小容量比较,如果jump已经大于这个最小空间并且没有大于MAX_ARRAY_SIZE最大容量。则是直接通过copyOf去进行扩容了。然后再通过copyof方法去对新空间进行操作,但这里我不明白是为什么会存在这里的if判断我看到在调用这个grow方法的时候都是进行了tail == head判断在进行扩容的,难道是为了限制并发操作的内容?

​ 下面我们来梳理下这个扩容赋值的操作,因为现在已经有新的容量了,所以需要将原来的在最后面的按顺序再调整到新容量的后面同时head也需要该。newSpace = newCapacity - oldCapacity ,这个newSpace 就是新增加的空间,然后将以前数组从head位置开始赋值到head + newSpace这个开始位置一直到oldCapacity - head。其是就是将head后面的元素往后推newSpace个位置。

​ 再来看下计算所得的空间如果小于needed最小的额外增加空间或超过最大空间了,其是调用newCapacity方法。

4)、newCapacity(int needed, int jump)

private int newCapacity(int needed, int jump) {
    final int oldCapacity = elements.length, minCapacity;
    if ((minCapacity = oldCapacity + needed) - MAX_ARRAY_SIZE > 0) {
        if (minCapacity < 0)
            throw new IllegalStateException("Sorry, deque too big");
        return Integer.MAX_VALUE;
    }
    if (needed > jump)
        return minCapacity;
    return (oldCapacity + jump - MAX_ARRAY_SIZE < 0)
        ? oldCapacity + jump
        : MAX_ARRAY_SIZE;
}

​ 首先是扩容的如果原来空间加上需要扩容的最小空间后溢出了,就返回Integer.MAX_VALUE。没有溢出&needed更大,就返回[oldCapacity + needed = minCapacity]空间,如果jump更大,则返回oldCapacity + jump。

5)、addLast(E e)

public void addLast(E e) {
    if (e == null)
        throw new NullPointerException();
    final Object[] es = elements;
    es[tail] = e;
    if (head == (tail = inc(tail, es.length)))
        grow(1);
}

​ 这里是添加到tail,前面提过其是从前往后,所以其实inc方法,同时可以看大这里也是判断head与tail相等再扩容的。

6)、inc(int i, int modulus)

static final int inc(int i, int modulus) {
    if (++i >= modulus) i = 0;
    return i;
}

​ 这里就是++i同时如果到达modulus也就本次设置长度,就往前归0,也就是双端循环队列。

7)、elementAt(Object[] es, int i)

static final <E> E elementAt(Object[] es, int i) {
    return (E) es[i];
}

​ 这个就是获取指定index位置的值。

8)、nonNullElementAt(Object[] es, int i)

static final <E> E nonNullElementAt(Object[] es, int i) {
    @SuppressWarnings("unchecked") E e = (E) es[i];
    if (e == null)
        throw new ConcurrentModificationException();
    return e;
}

​ 判断指定位置是否为null,为空抛异常,不为空则返回该位置的值。

9)、offerFirst(E e)

public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}

​ 这个将值添加到first队首位置,其直接调用的addFirst方法,不过其添加了一个tru返回。

10)、offerLast(E e)

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

​ 添加再last队尾位置。

11)、pollFirst()

public E pollFirst() {
    final Object[] es;
    final int h;
    E e = elementAt(es = elements, h = head);
    if (e != null) {
        es[h] = null;
        head = inc(h, es.length);
    }
    return e;
}

​ 这个就是移除队首的元素,然后将head+1同时返回原来队首位置的元素。

12)、pollLast()

public E pollLast() {
    final Object[] es;
    final int t;
    E e = elementAt(es = elements, t = dec(tail, es.length));
    if (e != null)
        es[tail = t] = null;
    return e;
}

​ 删除&返回队尾的元素,同时tail-1。

13)、removeFirst()

public E removeFirst() {
    E e = pollFirst();
    if (e == null)
        throw new NoSuchElementException();
    return e;
}

​ 删除&返回队首位置的元素,可以看到其是调用的pollFirst方法去操作的。不过这里如果元素为null是会抛出NoSuchElementException异常的。

14)、removeLast()

public E removeLast() {
    E e = pollLast();
    if (e == null)
        throw new NoSuchElementException();
    return e;
}

​ 移除&返回队尾元素。

15)、getFirst()

public E getFirst() {
    E e = elementAt(elements, head);
    if (e == null)
        throw new NoSuchElementException();
    return e;
}

​ 获取队首元素。

16)、getLast()

public E getLast() {
    final Object[] es = elements;
    E e = elementAt(es, dec(tail, es.length));
    if (e == null)
        throw new NoSuchElementException();
    return e;
}

​ 获取队尾元素。

17)、peekFirst()

public E peekFirst() {
    return elementAt(elements, head);
}

​ 返回队首元素,可以看到值与前面的get方法不同的是这里是直接返回,并不会为空抛出异常。

18)、peekLast()

public E peekLast() {
    final Object[] es;
    return elementAt(es = elements, dec(tail, es.length));
}

​ 返回队尾位置的值,不过这里与队首不同的是这里是调用了dec方法来获取index,展示因为,可能并没有在last位置添加元素,所以要进行移动完全到es.length-1这个位置去获取first位置,所以这里也是体现了双端循环队列。

19)、sub(int i, int j, int modulus)

static final int sub(int i, int j, int modulus) {
    if ((i -= j) < 0) i += modulus;
    return i;
}

​ 这个就是将i移动j个位置。首先是[i = i - j] 如果i小于0表明j>i再将其去[ i = i + modulus ],这个modulus一般是数组的长度。这里例如说需要删除i位置值。由于是删除,所以需要将该位置由其前面或者后面的内容往前或后移,并且由于是双端循环列表,看能也会前后移都存在。所以这里,例如我们假设如果i是需要删除的位置,j=head,i比j大(简单情况),所以这里的[ i = i - j ]其实就是表明在i的前面有多少个元素,其实就是需要从j开始将其后的[ i = i - j ]个元素将这段往后移动一位,然后将原来head位置设置为null,就实现了删除位置i的元素。现在我们再来假设另一种情况,如果i比head要小(表明其是在tail最前面那段),这个时候,首先是[ i = i + modulus ] 现在i已经是一个负数了,这里需要明白这个i的绝对值表明tail - head中间这段没有数据的距离,然后再通过与数组长度相加,就得到了这个i前面有多少个数据了(双端循环队列)。

20)、delete(int i)

boolean delete(int i) {
    final Object[] es = elements;
    final int capacity = es.length;
    final int h, t;
    // number of elements before to-be-deleted elt
    final int front = sub(i, h = head, capacity);
    // number of elements after to-be-deleted elt
    final int back = sub(t = tail, i, capacity) - 1;
    if (front < back) {
        // move front elements forwards
        if (h <= i) {
            System.arraycopy(es, h, es, h + 1, front);
        } else { // Wrap around
            System.arraycopy(es, 0, es, 1, i);
            es[0] = es[capacity - 1];
            System.arraycopy(es, h, es, h + 1, front - (i + 1));
        }
        es[h] = null;
        head = inc(h, capacity);
        return false;
    } else {
        // move back elements backwards
        tail = dec(t, capacity);
        if (i <= tail) {
            System.arraycopy(es, i + 1, es, i, back);
        } else { // Wrap around
            System.arraycopy(es, i + 1, es, i, capacity - (i + 1));
            es[capacity - 1] = es[0];
            System.arraycopy(es, 1, es, 0, t - 1);
        }
        es[tail] = null;
        return true;
    }
}

​ 这个是删除指定位置的值。这里首先是通过head的值调用前面sub方法来获取i位置前面的元素个数,再通过tail的再来获取i后面的元素个数。再判断如果后面的更多,又会有另一种判断,即i是在head 前面还是后面,如果是在head前面就比较好操作了,就像前面说的,将这段都往后移动一位。如果是在前面,i在0到tail那段,就首先是将 0到 i这段往后移动一个单位,就会剩下0位置,所以需要通过 [ es[capacity - 1] = es[0] ]将capacity - 1移动到0这个位置了,然后再将head这段往后移动一位。通过是front > back,这就差不多是上面的反操作。通过这种比较判断就减少了元素复制移动的个数了

21)、removeFirstOccurrence(Object o)

public boolean removeFirstOccurrence(Object o) {
    if (o != null) {
        final Object[] es = elements;
        for (int i = head, end = tail, to = (i <= end) ? end : es.length;
             ; i = 0, to = end) {
            for (; i < to; i++)
                if (o.equals(es[i])) {
                    delete(i);
                    return true;
                }
            if (to == end) break;
        }
    }
    return false;
}

​ 删除元素o,其是队首到队尾,发现的第一个元素。然后可以看到其是通过equals方法判断找到对应位置,再通过delete方法去删除。

22)、removeLastOccurrence(Object o)

public boolean removeLastOccurrence(Object o) {
    if (o != null) {
        final Object[] es = elements;
        for (int i = tail, end = head, to = (i >= end) ? end : 0;
             ; i = es.length, to = end) {
            for (i--; i > to - 1; i--)
                if (o.equals(es[i])) {
                    delete(i);
                    return true;
                }
            if (to == end) break;
        }
    }
    return false;
}

​ 从队尾到队首发现的第一个。

23)、add(E e)

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

​ 包装的addLast(e)方法,然后返回一个true。

24)、offer(E e)

public boolean offer(E e) {
    return offerLast(e);
}

​ 这个是包的offerLast(e),但其是再包的addLast方法,这个前面有提过。(实现Deque接口的方法)

25)、remove()

public E remove() {
    return removeFirst();
}

​ 删除队首的元素。

26)、pollFirst()

public E poll() {
    return pollFirst();
}

​ 包的pollFirst()方法。(实现Deque接口的方法)

27)、element()

public E element() {
    return getFirst();
}

​ 获取队首元素,包的getFirst()。

28)、peek()

public E peek() {
    return peekFirst();
}

​ 包的peekFirst()方法。

29)、addFirst(e)

public void push(E e) {
    addFirst(e);
}

30)、pop()

public E pop() {
    return removeFirst();
}

31)、size()

public int size() {
    return sub(tail, head, elements.length);
}

​ 获取现在有多少个元素,通过前面的sub方法计算。