美文网首页
Java集合-ArrayDeque源码深入浅出分析

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

作者: Misout | 来源:发表于2017-08-29 16:16 被阅读231次

    概要

    最近持续阅读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<String> queue = new ArrayDeque<>();
    queue.add("语文");
    queue.add("英语");
    queue.add("数学");
    

    存储结构图如下:

    ArrayDeque数据存储结构

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

    public class ArrayDeque<E> extends AbstractCollection<E>
                implements Deque<E>, 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<? extends E> 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源码深入浅出分析

          本文链接:https://www.haomeiwen.com/subject/hevodxtx.html