• 【源码阅读】Java集合之三


    最全的Java后端知识体系 https://www.pdai.tech, 每天更新中...

    在这里插入图片描述

    Java 源码阅读的第一步是Collection框架源码,这也是面试基础中的基础; 针对Collection的源码阅读写一个系列的文章,本文是第三篇ArrayDeque。 ---@pdai

    JDK版本

    JDK 1.8.0_110

    概述总结

    • ArrayDeque是可变长Array, 实现了Deque接口;Deque是"double ended queue", 表示双向的队列,英文读作"deck";
    • Deque 继承自 Queue接口,除了支持Queue的方法之外,还支持insert, removeexamine操作,由于Deque是双向的,所以可以对队列的头和尾都进行操作,它同时也支持两组格式,一组是抛出异常的实现;另外一组是返回值的实现(没有则返回null);
    • Java里有一个叫做Stack的类,却没有叫做Queue的类(它是个接口名字)。当需要使用栈时,Java已不推荐使用Stack,而是推荐使用更高效的ArrayDeque;既然Queue只是一个接口,当需要使用队列时也就首选ArrayDeque了(次选是LinkedList)。
    • ArrayDeque底层通过数组实现,为了满足可以同时在数组两端插入或删除元素的需求,该数组还必须是循环的,即循环数组(circular array),也就是说数组的任何一点都可能被看作起点或者终点;
    • ArrayDeque是非线程安全的(not thread-safe),当多个线程同时使用的时候,需要程序员手动同步;另外,该容器不允许放入null元素。
    • ArrayDeque也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险;
    • ArrayDeque底层的数据类型是Object[], Java泛型只是编译器提供的语法糖,所以这里的数组是一个Object数组,以便能够容纳任何类型的对象;

    类关系图

    ArrayDeque实现的接口和继承的类如下:

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

    Collection接口

    Collection接口操作分为如下几类:

    • Query Operations

      • int size();
      • boolean isEmpty();
      • boolean contains(Object o);
      • Iterator iterator();
      • Object[] toArray();
      • T[] toArray(T[] a);
    • Modification Operations

      • boolean add(E e);
      • boolean remove(Object o);
    • Bulk Operations

      • boolean containsAll(Collection<?> c);
      • boolean addAll(Collection<? extends E> c);
      • boolean removeAll(Collection<?> c);
      • default boolean removeIf(Predicate<? super E> filter)
      • boolean retainAll(Collection<?> c);
      • void clear();
    • Comparison and hashing

      • boolean equals(Object o);
      • int hashCode();
    • others

      • default Spliterator spliterator() {}
      • default Stream stream() {}
      • default Stream parallelStream() {}

    Queue

    Queue接口继承自Collection接口,除了最基本的Collection的方法之外,它还支持额外的insertion, extractioninspection操作。这里有两组格式,共6个方法,一组是抛出异常的实现;另外一组是返回值的实现(没有则返回null)。

    Throws exception Returns special value
    Insert add(e) offer(e)
    Remove remove() poll()
    Examine element() peek()

    Deque

    Deque 继承自 Queue接口,除了支持Queue的方法之外,还支持insert, removeexamine操作,由于Deque是双向的,所以可以对队列的头和尾都进行操作,它同时也支持两组格式,一组是抛出异常的实现;另外一组是返回值的实现(没有则返回null)。共12个方法如下:

    ||First Element (Head)|| Last Element (Tail) ||
    |--------|--------|--------|--------|
    ||Throws exception| Special value| Throws exception| Special value |
    |Insert| addFirst(e)| offerFirst(e)| addLast(e)| offerLast(e) |
    |Remove|removeFirst()| pollFirst()| removeLast()| pollLast() |
    |Examine| getFirst()| peekFirst()| getLast()| peekLast() |

    当把Deque当做FIFO的queue来使用时,元素是从deque的尾部添加,从头部进行删除的; 所以deque的部分方法是和queue是等同的。具体如下:

    Queue Method Equivalent Deque Method
    add(e) addLast(e)
    offer(e) offerLast(e)
    remove() removeFirst()
    poll() pollFirst()
    element() getFirst()
    peek() peekFirst()

    Deque的含义是“double ended queue”,即双端队列,它既可以当作栈使用,也可以当作队列使用。

    下表列出了DequeQueue相对应的接口:

    Queue Method Equivalent Deque Method 说明
    add(e) addLast(e) 向队尾插入元素,失败则抛出异常
    offer(e) offerLast(e) 向队尾插入元素,失败则返回false
    remove() removeFirst() 获取并删除队首元素,失败则抛出异常
    poll() pollFirst() 获取并删除队首元素,失败则返回null
    element() getFirst() 获取但不删除队首元素,失败则抛出异常
    peek() peekFirst() 获取但不删除队首元素,失败则返回null

    下表列出了DequeStack对应的接口:

    Stack Method Equivalent Deque Method 说明
    push(e) addFirst(e) 向栈顶插入元素,失败则抛出异常
    offerFirst(e) 向栈顶插入元素,失败则返回false
    pop() removeFirst() 获取并删除栈顶元素,失败则抛出异常
    pollFirst() 获取并删除栈顶元素,失败则返回null
    peek() peekFirst() 获取但不删除栈顶元素,失败则抛出异常
    peekFirst() 获取但不删除栈顶元素,失败则返回null

    上面两个表共定义了Deque的12个接口。添加,删除,取值都有两套接口,它们功能相同,区别是对失败情况的处理不同。一套接口遇到失败就会抛出异常,另一套遇到失败会返回特殊值(falsenull。除非某种实现对容量有限制,大多数情况下,添加操作是不会失败的。虽然Deque的接口有12个之多,但无非就是对容器的两端进行操作,或添加,或删除,或查看。明白了这一点讲解起来就会非常简单。

    类的实现

    底层数据结构

    从名字可以看出ArrayDeque底层通过数组实现,为了满足可以同时在数组两端插入或删除元素的需求,该数组还必须是循环的,即循环数组(circular array),也就是说数组的任何一点都可能被看作起点或者终点。ArrayDeque是非线程安全的(not thread-safe),当多个线程同时使用的时候,需要程序员手动同步;另外,该容器不允许放入null元素。

        /**
         * The array in which the elements of the deque are stored.
         * The capacity of the deque is the length of this array, which is
         * always a power of two. The array is never allowed to become
         * full, except transiently within an addX method where it is
         * resized (see doubleCapacity) immediately upon becoming full,
         * thus avoiding head and tail wrapping around to equal each
         * other.  We also guarantee that all array cells not holding
         * deque elements are always null.
         */
        transient Object[] elements; // non-private to simplify nested class access
    
        /**
         * The index of the element at the head of the deque (which is the
         * element that would be removed by remove() or pop()); or an
         * arbitrary number equal to tail if the deque is empty.
         */
        transient int head;
    
        /**
         * The index at which the next element would be added to the tail
         * of the deque (via addLast(E), add(E), or push(E)).
         */
        transient int tail;
    
        /**
         * The minimum capacity that we'll use for a newly created deque.
         * Must be a power of 2.
         */
        private static final int MIN_INITIAL_CAPACITY = 8;
    

    head指向首端第一个有效元素,tail指向尾端第一个可以插入元素的空位。因为是循环数组,所以head不一定总等于0,tail也不一定总是比head大。

    基础数据结构方法

    下面再说说扩容函数doubleCapacity(),其逻辑是申请一个更大的数组(原数组的两倍),然后将原数组复制过去。

        /**
         * Allocates empty array to hold the given number of elements.
         *
         * @param numElements  the number of elements to hold
         */
        private void allocateElements(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
            }
            elements = new Object[initialCapacity];
        }
    
        /**
         * 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; // head右边元素的个数
    	    int newCapacity = n << 1;//原空间的2倍
    	    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 = (E[])a;
    	    head = 0;
    	    tail = n;
    	}
    
        /**
         * Copies the elements from our element array into the specified array,
         * in order (from first to last element in the deque).  It is assumed
         * that the array is large enough to hold all elements in the deque.
         *
         * @return its argument
         */
        private <T> T[] copyElements(T[] a) {
            if (head < tail) {
                System.arraycopy(elements, head, a, 0, size());
            } else if (head > tail) {
                int headPortionLen = elements.length - head;
                System.arraycopy(elements, head, a, 0, headPortionLen);
                System.arraycopy(elements, 0, a, headPortionLen, tail);
            }
            return a;
        }
    

    构造函数

        /**
         * Constructs an empty array deque with an initial capacity
         * sufficient to hold 16 elements.
         */
        public ArrayDeque() {
            elements = new Object[16];
        }
    
        /**
         * Constructs an empty array deque with an initial capacity
         * sufficient to hold the specified number of elements.
         *
         * @param numElements  lower bound on initial capacity of the deque
         */
        public ArrayDeque(int numElements) {
            allocateElements(numElements);
        }
    
        /**
         * Constructs a deque containing the elements of the specified
         * collection, in the order they are returned by the collection's
         * iterator.  (The first element returned by the collection's
         * iterator becomes the first element, or <i>front</i> of the
         * deque.)
         *
         * @param c the collection whose elements are to be placed into the deque
         * @throws NullPointerException if the specified collection is null
         */
        public ArrayDeque(Collection<? extends E> c) {
            allocateElements(c.size());
            addAll(c);
        }
    

    基本的插入和取出方法

    主要都是基于如下四个方法addFirst(), addLast(), pollFirst(), pollLast()

    addFirst()

    addFirst(E e)的作用是在Deque的首端插入元素,也就是在head的前面插入元素,在空间足够且下标没有越界的情况下,只需要将elements[--head] = e即可。

    实际需要考虑:1.空间是否够用,以及2.下标是否越界的问题。

    //addFirst(E e)
    public void addFirst(E e) {
        if (e == null)//不允许放入null
            throw new NullPointerException();
        elements[head = (head - 1) & (elements.length - 1)] = e;//2.下标是否越界
        if (head == tail)//1.空间是否够用
            doubleCapacity();//扩容
    }
    

    上述代码我们看到,空间问题是在插入之后解决的,因为tail总是指向下一个可插入的空位,也就意味着elements数组至少有一个空位,所以插入元素的时候不用考虑空间问题。

    下标越界的处理解决起来非常简单,head = (head - 1) & (elements.length - 1)就可以了,这段代码相当于取余,同时解决了head为负值的情况。因为elements.length必需是2的指数倍,elements - 1就是二进制低位全1,跟head - 1相与之后就起到了取模的作用,如果head - 1为负数(其实只可能是-1),则相当于对其取相对于elements.length的补码。

    addLast()

    addLast(E e)的作用是在Deque的尾端插入元素,也就是在tail的位置插入元素,由于tail总是指向下一个可以插入的空位,因此只需要elements[tail] = e;即可。插入完成后再检查空间,如果空间已经用光,则调用doubleCapacity()进行扩容。

    public void addLast(E e) {
        if (e == null)//不允许放入null
            throw new NullPointerException();
        elements[tail] = e;//赋值
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)//下标越界处理
            doubleCapacity();//扩容
    }
    

    pollFirst()

    pollFirst()的作用是删除并返回Deque首端元素,也即是head位置处的元素。如果容器不空,只需要直接返回elements[head]即可,当然还需要处理下标的问题。由于ArrayDeque中不允许放入null,当elements[head] == null时,意味着容器为空。

    public E pollFirst() {
        E result = elements[head];
        if (result == null)//null值意味着deque为空
            return null;
        elements[h] = null;//let GC work
        head = (head + 1) & (elements.length - 1);//下标越界处理
        return result;
    }
    

    pollLast()

    pollLast()的作用是删除并返回Deque尾端元素,也即是tail位置前面的那个元素。

    public E pollLast() {
        int t = (tail - 1) & (elements.length - 1);//tail的上一个位置是最后一个元素
        E result = elements[t];
        if (result == null)//null值意味着deque为空
            return null;
        elements[t] = null;//let GC work
        tail = t;
        return result;
    }
    

    peekFirst()

    peekFirst()的作用是返回但不删除Deque首端元素,也即是head位置处的元素,直接返回elements[head]即可。

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

    peekLast()

    peekLast()的作用是返回但不删除Deque尾端元素,也即是tail位置前面的那个元素。

    public E peekLast() {
        return elements[(tail - 1) & (elements.length - 1)];
    }
    

    队列(Queue)方法

    add(E e)

        /**
         * Inserts the specified element at the end of this deque.
         *
         * <p>This method is equivalent to {@link #addLast}.
         *
         * @param e the element to add
         * @return {@code true} (as specified by {@link Collection#add})
         * @throws NullPointerException if the specified element is null
         */
        public boolean add(E e) {
            addLast(e);
            return true;
        }
    
    

    offer(E e)

    
        /**
         * Inserts the specified element at the end of this deque.
         *
         * <p>This method is equivalent to {@link #offerLast}.
         *
         * @param e the element to add
         * @return {@code true} (as specified by {@link Queue#offer})
         * @throws NullPointerException if the specified element is null
         */
        public boolean offer(E e) {
            return offerLast(e);
        }
    

    remove()

        /**
         * Retrieves and removes the head of the queue represented by this deque.
         *
         * This method differs from {@link #poll poll} only in that it throws an
         * exception if this deque is empty.
         *
         * <p>This method is equivalent to {@link #removeFirst}.
         *
         * @return the head of the queue represented by this deque
         * @throws NoSuchElementException {@inheritDoc}
         */
        public E remove() {
            return removeFirst();
        }
    

    poll()

        /**
         * Retrieves and removes the head of the queue represented by this deque
         * (in other words, the first element of this deque), or returns
         * {@code null} if this deque is empty.
         *
         * <p>This method is equivalent to {@link #pollFirst}.
         *
         * @return the head of the queue represented by this deque, or
         *         {@code null} if this deque is empty
         */
        public E poll() {
            return pollFirst();
        }
    

    element()

        /**
         * Retrieves, but does not remove, the head of the queue represented by
         * this deque.  This method differs from {@link #peek peek} only in
         * that it throws an exception if this deque is empty.
         *
         * <p>This method is equivalent to {@link #getFirst}.
         *
         * @return the head of the queue represented by this deque
         * @throws NoSuchElementException {@inheritDoc}
         */
        public E element() {
            return getFirst();
        }
    

    peek()

        /**
         * Retrieves, but does not remove, the head of the queue represented by
         * this deque, or returns {@code null} if this deque is empty.
         *
         * <p>This method is equivalent to {@link #peekFirst}.
         *
         * @return the head of the queue represented by this deque, or
         *         {@code null} if this deque is empty
         */
        public E peek() {
            return peekFirst();
        }
    

    栈(Stack)方法

    push(E e)

        /**
         * Pushes an element onto the stack represented by this deque.  In other
         * words, inserts the element at the front of this deque.
         *
         * <p>This method is equivalent to {@link #addFirst}.
         *
         * @param e the element to push
         * @throws NullPointerException if the specified element is null
         */
        public void push(E e) {
            addFirst(e);
        }
    

    pop()

        /**
         * Pops an element from the stack represented by this deque.  In other
         * words, removes and returns the first element of this deque.
         *
         * <p>This method is equivalent to {@link #removeFirst()}.
         *
         * @return the element at the front of this deque (which is the top
         *         of the stack represented by this deque)
         * @throws NoSuchElementException {@inheritDoc}
         */
        public E pop() {
            return removeFirst();
        }
    

    集合(Collection)方法

    size()

        /**
         * Returns the number of elements in this deque.
         *
         * @return the number of elements in this deque
         */
        public int size() {
            return (tail - head) & (elements.length - 1);
        }
    

    isEmpty()

        /**
         * Returns {@code true} if this deque contains no elements.
         *
         * @return {@code true} if this deque contains no elements
         */
        public boolean isEmpty() {
            return head == tail;
        }
    

    iterator()

        /**
         * Returns an iterator over the elements in this deque.  The elements
         * will be ordered from first (head) to last (tail).  This is the same
         * order that elements would be dequeued (via successive calls to
         * {@link #remove} or popped (via successive calls to {@link #pop}).
         *
         * @return an iterator over the elements in this deque
         */
        public Iterator<E> iterator() {
            return new DeqIterator();
        }
    
        public Iterator<E> descendingIterator() {
            return new DescendingIterator();
        }
    

    contains(Object o)

    
        /**
         * Returns {@code true} if this deque contains the specified element.
         * More formally, returns {@code true} if and only if this deque contains
         * at least one element {@code e} such that {@code o.equals(e)}.
         *
         * @param o object to be checked for containment in this deque
         * @return {@code true} if this deque contains the specified element
         */
        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;
        }
    

    remove(Object o)

        /**
         * Removes a single instance of the specified element from this deque.
         * If the deque does not contain the element, it is unchanged.
         * More formally, removes the first element {@code e} such that
         * {@code o.equals(e)} (if such an element exists).
         * Returns {@code true} if this deque contained the specified element
         * (or equivalently, if this deque changed as a result of the call).
         *
         * <p>This method is equivalent to {@link #removeFirstOccurrence(Object)}.
         *
         * @param o element to be removed from this deque, if present
         * @return {@code true} if this deque contained the specified element
         */
        public boolean remove(Object o) {
            return removeFirstOccurrence(o);
        }
    
    

    clear()

        /**
         * Removes all of the elements from this deque.
         * The deque will be empty after this call returns.
         */
        public void clear() {
            int h = head;
            int t = tail;
            if (h != t) { // clear all cells
                head = tail = 0;
                int i = h;
                int mask = elements.length - 1;
                do {
                    elements[i] = null;
                    i = (i + 1) & mask;
                } while (i != t);
            }
        }
    

    toArray()

        /**
         * Returns an array containing all of the elements in this deque
         * in proper sequence (from first to last element).
         *
         * <p>The returned array will be "safe" in that no references to it are
         * maintained by this deque.  (In other words, this method must allocate
         * a new array).  The caller is thus free to modify the returned array.
         *
         * <p>This method acts as bridge between array-based and collection-based
         * APIs.
         *
         * @return an array containing all of the elements in this deque
         */
        public Object[] toArray() {
            return copyElements(new Object[size()]);
        }
    
    

    toArray(T[] a)

        /**
         * Returns an array containing all of the elements in this deque in
         * proper sequence (from first to last element); the runtime type of the
         * returned array is that of the specified array.  If the deque fits in
         * the specified array, it is returned therein.  Otherwise, a new array
         * is allocated with the runtime type of the specified array and the
         * size of this deque.
         *
         * <p>If this deque fits in the specified array with room to spare
         * (i.e., the array has more elements than this deque), the element in
         * the array immediately following the end of the deque is set to
         * {@code null}.
         *
         * <p>Like the {@link #toArray()} method, this method acts as bridge between
         * array-based and collection-based APIs.  Further, this method allows
         * precise control over the runtime type of the output array, and may,
         * under certain circumstances, be used to save allocation costs.
         *
         * <p>Suppose {@code x} is a deque known to contain only strings.
         * The following code can be used to dump the deque into a newly
         * allocated array of {@code String}:
         *
         *  <pre> {@code String[] y = x.toArray(new String[0]);}</pre>
         *
         * Note that {@code toArray(new Object[0])} is identical in function to
         * {@code toArray()}.
         *
         * @param a the array into which the elements of the deque are to
         *          be stored, if it is big enough; otherwise, a new array of the
         *          same runtime type is allocated for this purpose
         * @return an array containing all of the elements in this deque
         * @throws ArrayStoreException if the runtime type of the specified array
         *         is not a supertype of the runtime type of every element in
         *         this deque
         * @throws NullPointerException if the specified array is null
         */
        @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            int size = size();
            if (a.length < size)
                a = (T[])java.lang.reflect.Array.newInstance(
                        a.getClass().getComponentType(), size);
            copyElements(a);
            if (a.length > size)
                a[size] = null;
            return a;
        }
    
  • 相关阅读:
    java 大数据处理类 BigDecimal 解析
    关于纠正 C/C++ 之前在函输内改变 变量的一个错误想法。
    C++ 制作 json 数据 并 传送给服务端(Server) 的 php
    介绍一个很爽的 php 字符串特定检索函数---strpos()
    如何 判断 设备 是否 连接 上 了 wifi
    android 通过访问 php 接受 or 传送数据
    正则匹配抓取input 隐藏输入项和 <td>标签内的内容
    手把手教你Chrome扩展开发:本地存储篇
    HTML5之本地存储localstorage
    初尝CDN:什么是分布式服务节点?
  • 原文地址:https://www.cnblogs.com/pengdai/p/9229888.html
Copyright © 2020-2023  润新知