ArrayDeque部分源码剖析

Posted rotk2015

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ArrayDeque部分源码剖析相关的知识,希望对你有一定的参考价值。

笔者所用JDK为 RedHatOpenJDK-8u282-win64 版。

  1. ArrayDeque,数组实现的双端队列。核心思想是维护头尾指针,将数组变为循环数组(头指针指向头元素,尾指针指向尾元素后的空位)。无法按索引取值(因为没有固定的索引)。不允许插入 Null。用作栈时,比Stack快;用作队列时,比LinkedList快。缺点是:线程不安全,空间浪费。

  2. 扩容机制:双倍扩容,数组长度永远是2的幂(最小8,好处是扩容、取模时只需位运算即可)。

  3. 构造器函数,无参时,创建长度为16的数组;有参时,调用allocateElements()

    public ArrayDeque() 
        elements = new Object[16];
    
    public ArrayDeque(int numElements) 
        allocateElements(numElements);
    
    public ArrayDeque(Collection<? extends E> c) 
        allocateElements(c.size());
        addAll(c);
    
    
  4. allocateElements(),按输入int形参,调用calculateSize(),分配长度为2的幂次的数组。

    /**
     * Allocates empty array to hold the given number of elements.
     *
     * @param numElements  the number of elements to hold
     */
    private void allocateElements(int numElements) 
        elements = new Object[calculateSize(numElements)];
    
    
  5. calculateSize(),若 numElements 小于8,分配8;否则,分配大于 numElements 的最小2幂次(若 numElements 已为2幂次,翻倍),最多为2^30。源码使用位运算实现,通过不断移位2的幂次再或,将 numElements 的最高位 1 向后传播

    private static final int MIN_INITIAL_CAPACITY = 8;
    private static int calculateSize(int numElements) 
        int initialCapacity = MIN_INITIAL_CAPACITY;
        // Find the best power of two to hold elements.
        // Tests "<=" because arrays aren't kept full.
        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
        
        return initialCapacity;
    
    
  6. addFirst()位运算实现取模避免 head 越界(说穿了就是在其等于-1时转换为1),若插入后数组存满(head==tail),调用 doubleCapacity() 扩容数组长翻倍

    那要是插入前就满了呢?该情况不会发生。首先,构造函数保证数组必有空余;其次,每次插入后都会进行判断,及时扩容。

    /**
     * Inserts the specified element at the front of this deque.
     *
     * @param e the element to add
     * @throws NullPointerException if the specified element is null
     */
    public void addFirst(E e) 
        if (e == null)
            throw new NullPointerException();
        elements[head = (head - 1) & (elements.length - 1)] = e;
        if (head == tail)
            doubleCapacity();
    
    
  7. doubleCapacity(),调用 native 方法 System.arraycopy(),先复制 head 右半段,再复制左半段。

    /**
     * Doubles the capacity of this deque.  Call only when full, i.e.,
     * when head and tail have wrapped around to become equal.
     */
    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;
    
    
  8. pollFirst(),从头部移出一个元素,若为null,返回null。

    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;
    
    

以上是关于ArrayDeque部分源码剖析的主要内容,如果未能解决你的问题,请参考以下文章

STL源码剖析(deque)

ArrayDeque 源码分析

JDK源码ArrayDeque源码分析

死磕 java集合之ArrayDeque源码分析

ArrayDeque源码详解

源码阅读(11):Java中主要的QueueDeque结构——ArrayDeque集合(下)