Java集合-ArrayDeque源码深入浅出分析

概要

最近持续阅读Java集合源码实现,学习,写文章,分析,画图,感觉写技术文章是个很有难度的活。既要自己弄懂,也要想尽办法,敲打任何文字或制作图片来简单的描述各种复杂的原理,为的是让读者更容易的了解每个实现的原理。这些就需要花费笔者很多时间,白天默默的在公司劳作,晚上还要抽出时间学习,并写成文章,真心觉得累。没办法,程序猿就得学的比别人更勤奋,更努力才行,否则未来就得被新人淘汰出局。技术的路上就需要多学习,多总结,多写文章分享,慢慢提升。

好了,说了一些感悟,进入正题吧。

在java.util包中有一个用于双端队列的集合类ArrayDeque,也是包中唯一的队列实现类,这个类大家平常用的比较少,我们还是来先看看它的源码实现,深入分析,一探究竟,为java.util.concurrent包中的BlockingQueue深入分析作准备。

来看下源码中关于ArrayDeque的英文说明如下:

Resizable-array implementation of the Deque interface. Array
deques have no capacity restrictions; they grow as necessary to support
usage. They are not thread-safe; in the absence of external
synchronization, they do not support concurrent access by multiple threads.
Null elements are prohibited. This class is likely to be faster than
Stack when used as a stack, and faster than LinkedList
when used as a queue.

解读关键点:

1、ArrayDeque是基于数组实现的双端队列,可以在头尾插入或取出元素,队列的容量无限制,满的时候会扩容。从这点可以看出ArrayDeque为非阻塞队列
2、非线程安全,如果有多线程访问,建议在外部进行同步。
3、不允许null元素插入。
4、如果ArrayDeque用作栈,它会比Stack类要快,如果用作队列,它会比LinkedList快。

数据结构

来看下如下代码的数据存储结构:

ArrayDeque queue = new ArrayDeque<>();
queue.add("语文");
queue.add("英语");
queue.add("数学");

存储结构图如下:

Java集合-ArrayDeque源码深入浅出分析_第1张图片
ArrayDeque数据存储结构

ArrayDeque采用一个数组,并维护了head和tail两个索引数代表队列头和队列尾。来看下支持数据结构的属性及构造方法:

public class ArrayDeque extends AbstractCollection
            implements Deque, Cloneable, Serializable
{
    // 队列数组
    transient Object[] elements;
    
    // 队列头索引
    transient int head;
    
    // 队列尾索引
    transient int tail;
    
    // 允许创建队列的最小容量大小
    private static final int MIN_INITIAL_CAPACITY = 8;
    
    // 默认创建的队列容量为16
    public ArrayDeque() {
        elements = new Object[16];
    }
    
    // 创建指定容量的队列,如果这个数小于最小容量,则初始化容量为8,
    // 如果大于最小容量,则采用逻辑右移的操作,
    // 初始化为靠近所给容量值的2的幂次方大小
    public ArrayDeque(int numElements) {
        allocateElements(numElements);
    }
    
    public ArrayDeque(Collection c) {
        allocateElements(c.size());
        addAll(c);
    }
}

从源码中得出,ArrayDeque维护了一个Object的数组,允许存放任意实例对象,且队列的容量只能为2的幂次方的大小,默认初始容量为16。

基本操作

1、取元素操作

声明:下面介绍的操作只针对队列头部,队列尾部的操作是对称的,理解其一即可。

取元素操作方法有:

1、peak(),内部调用peekFirst():只取头元素,不删除。
2、poll(),内部调用pollFirst():取头元素的同时,将元素删除(设为null),如果队列没有任何元素,返回null
3、pop(),内部调用removeFirst():取头元素的同时,将元素删除。如果队列空,则抛出NoSuchElementException异常
4、element(),内部调用getFirst():只取头元素,不删除。如果队列空,则抛出NoSuchElementException异常

一般调用比较多的方法是poll方法,因为在队列为空时不会抛出异常,只返回null。来看下源码实现,比较简单,就不详述了:

public E peekFirst() {
    // elements[head] is null if deque empty
    return (E) elements[head];
}

public E pollFirst() {
    int h = head;
    @SuppressWarnings("unchecked")
    E result = (E) elements[h];
    // Element is null if deque empty
    if (result == null)
        return null;
    elements[h] = null;     // Must null out slot
    head = (h + 1) & (elements.length - 1);
    return result;
}

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

public E getFirst() {
    @SuppressWarnings("unchecked")
    E result = (E) elements[head];
    if (result == null)
        throw new NoSuchElementException();
    return result;
}

2、插入元素操作

声明:下面介绍的操作只针对队列尾部插入,队列头部的操作是对称的,理解其一即可。

插入元素的操作方法有:

1、add(),内部调用addLast():在队列尾插入元素,不允许插入null元素,否则抛出NullPointerException异常。如果超过容量,则扩容到原来2倍。
2、offer(),内部最终调用addLast():与add方法一致。
3、push(),内部调用addFirst():在队列头插入元素,不允许插入null元素,否则抛出NullPointerException异常。如果超过容量,则扩容到原来2倍。

3、扩容实现

扩容方法实现如下:

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];
    System.arraycopy(elements, p, a, 0, r);
    System.arraycopy(elements, 0, a, r, p);
    elements = a;
    head = 0;
    tail = n;
}

新容量为原来的2倍(n << 1就是乘2的动作)。然后采用本地方法System.arraycopy()分两次将全部元素复制到新数组中。
扩容的条件是:队列尾索引追上了队列头索引,也就是两个索引值相等,表示容量已经耗完。

Java集合-ArrayDeque源码深入浅出分析_第2张图片

你可能感兴趣的:(Java集合-ArrayDeque源码深入浅出分析)