zoukankan      html  css  js  c++  java
  • ArrayDeque的使用与底层原理

    参考:

    https://blog.csdn.net/chengqiuming/article/details/70139127

    https://www.cnblogs.com/chenglc/p/10722304.html

    https://blog.csdn.net/ted_cs/article/details/82926423

    ArrayDeque集合的妙用

    Deque接口是Queue接口的子接口,它代表一个双端队列,该队列允许从两端来操作队列中的元素。Deque不仅可以当成双端队列使用,而且可以当成栈来使用。

            ArrayDeque集合是Deque接口的实现类,它是一个基于数组的双端队列,创建Deque时同样可以指定一个numElements参数,该参数用于指定Object[]数组的长度;如果不指定该参数,Deque底层数组长度为16。

            ArrayDeque集合既可当队列使用,也可当栈使用,本篇将演示这两种典型的用法。

    一 ArrayDeque集合当栈来使用。

    1 代码示例

    import java.util.*;
     
    public class ArrayDequeStack
    {
        public static void main(String[] args)
        {
            ArrayDeque stack = new ArrayDeque();
            // 依次将三个元素push入"栈"
            stack.push("循循渐进Linux");
            stack.push("小学语文");
            stack.push("时间简史");
            // 输出:[时间简史, 小学语文, 循循渐进Linux]
            System.out.println(stack);
            // 访问第一个元素,但并不将其pop出"栈",输出:时间简史
            System.out.println(stack.peek());
            // 依然输出:[时间简史, 小学语文, 循循渐进Linux]
            System.out.println(stack);
            // pop出第一个元素,输出:时间简史
            System.out.println(stack.pop());
            // 输出:[小学语文, 循循渐进Linux]
            System.out.println(stack);
        }
    }

    2 运行结果

    [时间简史, 小学语文, 循循渐进Linux]

    时间简史

    [时间简史, 小学语文, 循循渐进Linux]

    时间简史

    [小学语文, 循循渐进Linux] 

    3 代码说明

    上面的程序显示了ArrayDeque作为栈的行为。

    二 ArrayDeque集合当队列来使用

    1 代码示例

    import java.util.*;
     
    public class ArrayDequeQueue
    {
        public static void main(String[] args)
        {
            ArrayDeque queue = new ArrayDeque();
            // 依次将三个元素加入队列
            queue.offer("光头强");
            queue.offer("熊大");
            queue.offer("熊二");
            // 输出:[光头强, 熊大, 熊二]
            System.out.println(queue);
            // 访问队列头部的元素,但并不将其poll出队列"栈",输出:光头强
            System.out.println(queue.peek());
            // 依然输出:[光头强, 熊大, 熊二]
            System.out.println(queue);
            // poll出第一个元素,输出:光头强
            System.out.println(queue.poll());
            // 输出:[熊大, 熊二]
            System.out.println(queue);
        }
    }

    2 运行结果

    [光头强, 熊大, 熊二]

    光头强

    [光头强, 熊大, 熊二]

    光头强

    [熊大, 熊二]

    3 代码说明

    上面的程序显示了ArrayDeque作为队列的行为。 

    ArrayDeque类的使用详解

    ArrayDequeDeque接口的一个实现,使用了可变数组,所以没有容量上的限制。

    同时,ArrayDeque是线程不安全的,在没有外部同步的情况下,不能再多线程环境下使用。
    ArrayDequeDeque的实现类,可以作为栈来使用,效率高于Stack
    也可以作为队列来使用,效率高于LinkedList
    需要注意的是,ArrayDeque不支持null值。
     

    一、常用方法

    复制代码
    1.添加元素
            addFirst(E e)在数组前面添加元素
            addLast(E e)在数组后面添加元素
            offerFirst(E e) 在数组前面添加元素,并返回是否添加成功
            offerLast(E e) 在数组后天添加元素,并返回是否添加成功
    
      2.删除元素
            removeFirst()删除第一个元素,并返回删除元素的值,如果元素为null,将抛出异常
            pollFirst()删除第一个元素,并返回删除元素的值,如果元素为null,将返回null
            removeLast()删除最后一个元素,并返回删除元素的值,如果为null,将抛出异常
            pollLast()删除最后一个元素,并返回删除元素的值,如果为null,将返回null
            removeFirstOccurrence(Object o) 删除第一次出现的指定元素
            removeLastOccurrence(Object o) 删除最后一次出现的指定元素
       
    
       3.获取元素
            getFirst() 获取第一个元素,如果没有将抛出异常
            getLast() 获取最后一个元素,如果没有将抛出异常
       
    
        4.队列操作
            add(E e) 在队列尾部添加一个元素
            offer(E e) 在队列尾部添加一个元素,并返回是否成功
            remove() 删除队列中第一个元素,并返回该元素的值,如果元素为null,将抛出异常(其实底层调用的是removeFirst())
            poll()  删除队列中第一个元素,并返回该元素的值,如果元素为null,将返回null(其实调用的是pollFirst())
            element() 获取第一个元素,如果没有将抛出异常
            peek() 获取第一个元素,如果返回null
          
    
        5.栈操作
            push(E e) 栈顶添加一个元素
            pop(E e) 移除栈顶元素,如果栈顶没有元素将抛出异常
            
    
        6.其他
            size() 获取队列中元素个数
            isEmpty() 判断队列是否为空
            iterator() 迭代器,从前向后迭代
            descendingIterator() 迭代器,从后向前迭代
            contain(Object o) 判断队列中是否存在该元素
            toArray() 转成数组
            clear() 清空队列
            clone() 克隆(复制)一个新的队列
    复制代码

    二、源码分析

    结构

    ArrayDeque底层使用了数组来存储数据,同时用两个intheadtail来表示头部和尾部。

    不过需要注意的是tail并不是尾部元素的索引,而是尾部元素的下一位,即下一个将要被加入的元素的索引。

    复制代码
    //用数组存储元素
    transient Object[] elements; // non-private to simplify nested class access
    //头部元素的索引
    transient int head;
    //尾部下一个将要被加入的元素的索引
    transient int tail;
    //最小容量,必须为2的幂次方
    private static final int MIN_INITIAL_CAPACITY = 8;
    复制代码

    2. 构造方法 

    ArrayDeque有三个构造函数来初始化,除了无参的构造函数使用了默认容量,其它两个构造函数会通过allocateElements来计算初始容量。

    复制代码
    public ArrayDeque() {  
        elements = (E[]) new Object[16]; // 默认的数组长度大小  
    }  
      
    public ArrayDeque(int numElements) {  
        allocateElements(numElements); // 需要的数组长度大小  
    }  
      
    public ArrayDeque(Collection<? extends E> c) {  
        allocateElements(c.size()); // 根据集合来分配数组大小  
        addAll(c); // 把集合中元素放到数组中  
    }  
    复制代码

    3. 分配合适大小的数组 

    ArrayDeque 对数组的大小(即队列的容量)有特殊的要求,必须是 2^n。我们来看一下allocateElements方法。

    复制代码
    private void allocateElements(int numElements) {  
        int initialCapacity = MIN_INITIAL_CAPACITY;  
        // 找到大于需要长度的最小的2的幂整数。  
        // 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 = (E[]) new Object[initialCapacity];  
    }  
    复制代码
    这里的实现很有意思,对于一个小于2^30的值,经过五次右移和位或操作后,可以得到一个2^k - 1的值。最后再将这个值+1,得到2^k。通过这个方法,可以将一个任意的初始值转化为2^n的值,不过有一点不足在于,如果本身传进来的值就是2^n的值,那么经过转化会变成2^(n+1),所以我们在不用刻意去传入2^n的值。还有一点在于,如果传入的值大于等于2^30,那么经过转化会变成负值,即< 0,此时会把初始值设置为2^30,即最大的容量只有2^30

    4. 扩容 

    复制代码
    // 扩容为原来的2倍。  
    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];  
        // 既然是head和tail已经重合了,说明tail是在head的左边。  
        System.arraycopy(elements, p, a, 0, r); // 拷贝原数组从head位置到结束的数据  
        System.arraycopy(elements, 0, a, r, p); // 拷贝原数组从开始到head的数据  
        elements = (E[])a;  
        head = 0; // 重置head和tail为数据的开始和结束索引  
        tail = n;  
    }  
      
    // 拷贝该数组的所有元素到目标数组  
    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;  
    }  
    复制代码

    5. 添加元素 

    复制代码
    public void addFirst(E e) {  
        if (e == null)  
            throw new NullPointerException();  
        // 本来可以简单地写成head-1,但如果head为0,减1就变为-1了,和elements.length - 1进行与操作就是为了处理这种情况,这时结果为elements.length - 1。  
        elements[head = (head - 1) & (elements.length - 1)] = e;  
        if (head == tail) // head和tail不可以重叠  
            doubleCapacity();  
    }  
      
    public void addLast(E e) {  
        if (e == null)  
            throw new NullPointerException();  
        // tail位置是空的,把元素放到这。  
        elements[tail] = e;  
        // 和head的操作类似,为了处理临界情况 (tail为length - 1时),和length - 1进行与操作,结果为0。  
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)  
            doubleCapacity();  
    }  
      
    public boolean offerFirst(E e) {  
        addFirst(e);  
        return true;  
    }  
      
    public boolean offerLast(E e) {  
        addLast(e);  
        return true;  
    }  
    复制代码

    6. 删除元素 删除首尾元素:

    复制代码
    public E removeFirst() {  
        E x = pollFirst();  
        if (x == null)  
            throw new NoSuchElementException();  
        return x;  
    }  
      
    public E removeLast() {  
        E x = pollLast();  
        if (x == null)  
            throw new NoSuchElementException();  
        return x;  
    }  
      
    public E pollFirst() {  
        int h = head;  
        E result = elements[h]; // Element is null if deque empty  
        if (result == null)  
            return null;  
        // 表明head位置已为空  
        elements[h] = null;     // Must null out slot  
        head = (h + 1) & (elements.length - 1); // 处理临界情况(当h为elements.length - 1时),与后的结果为0。  
        return result;  
    }  
      
    public E pollLast() {  
        int t = (tail - 1) & (elements.length - 1); // 处理临界情况(当tail为0时),与后的结果为elements.length - 1。  
        E result = elements[t];  
        if (result == null)  
            return null;  
        elements[t] = null;  
        tail = t; // tail指向的是下个要添加元素的索引。  
        return result;  
    }  
    复制代码

    删除指定元素

    复制代码
    public boolean removeFirstOccurrence(Object o) {  
        if (o == null)  
            return false;  
        int mask = elements.length - 1;  
        int i = head;  
        E x;  
        while ( (x = elements[i]) != null) {  
            if (o.equals(x)) {  
                delete(i);  
                return true;  
            }  
            i = (i + 1) & mask; // 从头到尾遍历  
        }  
        return false;  
    }  
      
    public boolean removeLastOccurrence(Object o) {  
        if (o == null)  
            return false;  
        int mask = elements.length - 1;  
        int i = (tail - 1) & mask; // 末尾元素的索引  
        E x;  
        while ( (x = elements[i]) != null) {  
            if (o.equals(x)) {  
                delete(i);  
                return true;  
            }  
            i = (i - 1) & mask; // 从尾到头遍历  
        }  
        return false;  
    }  
    
    private void checkInvariants() { // 有效性检查  
        assert elements[tail] == null; // tail位置没有元素  
        assert head == tail ? elements[head] == null :  
            (elements[head] != null &&  
                elements[(tail - 1) & (elements.length - 1)] != null); // 如果head和tail重叠,队列为空;否则head位置有元素,tail-1位置有元素。  
        assert elements[(head - 1) & (elements.length - 1)] == null; // head-1的位置没有元素。  
    }  
      
    private boolean delete(int i) {  
        checkInvariants();  
        final E[] elements = this.elements;  
        final int mask = elements.length - 1;  
        final int h = head;  
        final int t = tail;  
        final int front = (i - h) & mask; // i前面的元素个数  
        final int back  = (t - i) & mask; // i后面的元素个数  
      
        // Invariant: head <= i < tail mod circularity  
        if (front >= ((t - h) & mask)) // i不在head和tail之间  
            throw new ConcurrentModificationException();  
      
        // Optimize for least element motion  
        if (front < back) { // i的位置靠近head,移动开始的元素,返回false。  
            if (h <= i) {  
                System.arraycopy(elements, h, elements, h + 1, front);  
            } else { // Wrap around  
                System.arraycopy(elements, 0, elements, 1, i);  
                elements[0] = elements[mask]; // 处理边缘元素  
                System.arraycopy(elements, h, elements, h + 1, mask - h);  
            }  
            elements[h] = null;  
            head = (h + 1) & mask; // head位置后移  
            return false;  
        } else { // i的位置靠近tail,移动末尾的元素,返回true。  
            if (i < t) { // Copy the null tail as well  
                System.arraycopy(elements, i + 1, elements, i, back);  
                tail = t - 1;  
            } else { // Wrap around  
                System.arraycopy(elements, i + 1, elements, i, mask - i);  
                elements[mask] = elements[0];  
                System.arraycopy(elements, 1, elements, 0, t);  
                tail = (t - 1) & mask;  
            }  
            return true;  
        }  
    }  
    复制代码

    7. 获取元素

    复制代码
    public E getFirst() {  
        E x = elements[head];  
        if (x == null)  
            throw new NoSuchElementException();  
        return x;  
    }  
      
    public E getLast() {  
        E x = elements[(tail - 1) & (elements.length - 1)]; // 处理临界情况(当tail为0时),与后的结果为elements.length - 1。  
        if (x == null)  
            throw new NoSuchElementException();  
        return x;  
    }  
      
    public E peekFirst() {  
        return elements[head]; // elements[head] is null if deque empty  
    }  
      
    public E peekLast() {  
        return elements[(tail - 1) & (elements.length - 1)];  
    }  
    复制代码

    8. 队列操作 

    复制代码
    public boolean add(E e) {  
        addLast(e);  
        return true;  
    }  
      
    public boolean offer(E e) {  
        return offerLast(e);  
    }  
      
    public E remove() {  
        return removeFirst();  
    }  
      
    public E poll() {  
        return pollFirst();  
    }  
      
    public E element() {  
        return getFirst();  
    }  
      
    public E peek() {  
        return peekFirst();  
    }  
    复制代码

    9. 栈操作 

    复制代码
    public void push(E e) {  
        addFirst(e);  
    }  
      
    public E pop() {  
        return removeFirst();  
    }  
    复制代码

    10. 集合方法 

    复制代码
    public int size() {  
        return (tail - head) & (elements.length - 1); // 和elements.length - 1进行与操作是为了处理当tail < head时的情况。  
    }  
      
    public boolean isEmpty() {  
        return head == tail; // tail位置的元素一定为空,head和tail相等,也为空。  
    }  
      
    // 向前迭代器  
    public Iterator<E> iterator() {  
        return new DeqIterator();  
    }  
      
    // 向后迭代器  
    public Iterator<E> descendingIterator() {  
        return new DescendingIterator();  
    }  
    
      private class DeqIterator implements Iterator<E> {  
      
          private int cursor = head;  
      
          private int fence = tail; // 迭代终止索引,同时也为了检测并发修改。  
      
          private int lastRet = -1; // 最近的next()调用返回的索引。据此可以定位到需要删除元素的位置。  
      
          public boolean hasNext() {  
              return cursor != fence;  
          }  
      
          public E next() {  
              if (cursor == fence)  
                  throw new NoSuchElementException();  
              E result = elements[cursor];  
              // This check doesn't catch all possible comodifications,  
              // but does catch the ones that corrupt traversal  
              if (tail != fence || result == null)  
                  throw new ConcurrentModificationException();  
              lastRet = cursor;  
              cursor = (cursor + 1) & (elements.length - 1); // 游标位置加1  
              return result;  
          }  
      
          public void remove() {  
              if (lastRet < 0)  
                  throw new IllegalStateException();  
              if (delete(lastRet)) { // 如果将元素从右往左移,需要将游标减1。  
                  cursor = (cursor - 1) & (elements.length - 1); // 游标位置回退1。  
    fence = tail; // 重置阀值。  
       }  
              lastRet = -1;  
          }  
      }  
    
    private class DescendingIterator implements Iterator<E> {  
      
        private int cursor = tail; // 游标开始索引为tail  
        private int fence = head; // 游标的阀值为head  
        private int lastRet = -1;  
      
        public boolean hasNext() {  
            return cursor != fence;  
        }  
      
        public E next() {  
            if (cursor == fence)  
                throw new NoSuchElementException();  
            cursor = (cursor - 1) & (elements.length - 1); // tail是下个添加元素的位置,所以要减1才是尾节点的索引。  
            E result = elements[cursor];  
            if (head != fence || result == null)  
                throw new ConcurrentModificationException();  
            lastRet = cursor;  
            return result;  
        }  
      
        public void remove() {  
            if (lastRet < 0)  
                throw new IllegalStateException();  
            if (!delete(lastRet)) { // 如果从左往右移,需要将游标加1。  
                cursor = (cursor + 1) & (elements.length - 1);  
                fence = head;  
            }  
            lastRet = -1;  
        }  
    }  
      
    public boolean contains(Object o) {  
        if (o == null)  
            return false; // ArrayDeque不可以存储null元素  
        int mask = elements.length - 1;  
        int i = head;  
        E x;  
        while ( (x = elements[i]) != null) {  
            if (o.equals(x))  
                return true;  
            i = (i + 1) & mask; // 处理临界情况  
        }  
        return false;  
    }  
      
    public boolean remove(Object o) {  
        return removeFirstOccurrence(o);  
    }  
      
    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);  
        }  
    }  
      
    public Object[] toArray() {  
        return copyElements(new Object[size()]); // 把所有元素拷贝到新创建的Object数组上,所以对返回数组的修改不会影响该双端队列。  
    }  
      
    public <T> T[] toArray(T[] a) {  
        int size = size();  
        if (a.length < size) // 目标数组大小不够  
            a = (T[])java.lang.reflect.Array.newInstance(  
                    a.getClass().getComponentType(), size); // 利用反射创建类型为T,大小为size的数组。  
    yElements(a); // 拷贝所有元素到目标数组。  
        if (a.length > size)  
            a[size] = null; // 结束标识  
        return a;  
    }  
    复制代码

    11. Object方法 

    复制代码
    public ArrayDeque<E> clone() {  
        try {  
            ArrayDeque<E> result = (ArrayDeque<E>) super.clone();  
            result.elements = Arrays.copyOf(elements, elements.length); // 深度复制。  
            return result;  
      
        } catch (CloneNotSupportedException e) {  
            throw new AssertionError();  
        }  
    }  
    复制代码

    ArrayDeque双端队列完全解析

    ArrayDeque双端队列完全解析

    重点:

    • 底层通过循环数组实现 俩个重要属性 head tail
    • 不能添加null值,不然会报空指针
    • 每次扩容都是2的n次方
    • 可以实现普通队列先进先出排序,也可以实现栈先进后出的排序
    • 特别留意,它里面通过二进制方式判断数组是否已满
      (tail = (tail + 1) & (elements.length - 1)) == head
    • 注意操作插入和移除时,有Throws exception 和 return Special value 俩种情况

    循环数组概念

    我们知道,ArrayDeque是通过数组实现队列功能 的;而且具有对数组头尾双端添加和移除对象的功能,但如果数组不能实现循环功能,会出现以下情况

    图一

    图一

    在构建一个ArrayDeque对象时,会初始化head和tail的值为0.当有对象加入数组时,tail的值加1,当有对象出列时,head的值会加1.
    而head值 == tail值 ,可以帮我们判断数组是已经满。

    如果数组容量固定的情况下,从尾追加数据,从头出列数据,会出现实际数组有空的单元,但却tail会超过数组容量的情况,这种现象称为“假溢出” ;但往往,不可能是一种容量固定的数组,一般会有实现自动扩容的方法,但即便可以扩容,按照上面的逻辑,数组容量不断扩大,tail值一直向后,但从头出列的数据越来起多,前面空的内存单造成的浪费更是不能忽略了。

    再往下想,不是说Deque接口实现了头和尾添加和删除数据的功能吗?那它不是可以从头添加数据,不就可以利用到前面已经出列的空的单元吗?
    但如果就是单纯的就是在往后追加数据呢?那前面空出的内存单元,就这样舍弃掉?简直“暴殄天物”。

    所以,为了解决数组单元浪费的问题,就产生了循环数组。且看图
    图二

    图二

    从上图可知,当tail值超过数组索引后,就回到了索引为0的地方,实现了内存单元循环利用。
    你可以想象成,数组尾和头尾首相连,形成逻辑上的”环形“。
    但同时,你要清楚,上图中的例子,是头部已经有出列,有空的单元时,tail值回到的索引0的地方,但如果索引为0的地方有值时,此时,想要实现新对象的保存,也只能重新去扩容了。
    可知,循环数组可以实现有限数组容量的的高效利用,但新值不会覆盖原值。

    讲到这里,如果有细心猿会现,我图一在初始化时,tail和head都是对应索引为0的数组,我说数据从尾部追加,那应该调用的是addlast方法,但上图添加数据分明是从索引0开始追加的,是按照数组顺序的,和实际情况不相符啊。而且,如果从后面追加数据的话,你的tail值怎么移动?

    正如猿发现的问题一样,确实,我上面的例子不够严谨,请看下图

    图三

    图三

    我先调用addLast方法,把1加入数组,1位于数组尾部
    我再调用addLast方法,把2加入数组,2替换1的位置,追加尾部,1向前存储一个单元
    我先调用addFirst方法,把A加入数组,A位于数组头部
    我再调用addFirst方法,把B加入数组,B替换A的位置,放置数组头部,A向后存储一个单元

    所以,严格来说,无论是addLast 还是addFirst方法,都不按照数组顺序加入数据的(按照索引顺序);而是根据头尾,新追加数据会占用头尾位置,原来头尾位置的数据后移或前移 ;

    那么,tail值和head值怎么移动呢?
    其实,每增加一个对象,tail值就会+1 ,每移除一个对象,head值+1 ;
    并非如图一那样,head和tail值都要从左到右移动的那种样式。
    tail值和head值可以理作为一个标的,最后,通过tail和head值来确定数组是否已满

    那么这个标的是怎么实现判断数组已满的呢?


    循环数组已满判断

    jdk源码里面,通过 (tail = (tail + 1) & (elements.length - 1)) == head 来判断数组已满 ,并且要求数组每次扩容的长度为2的n次方来使得上面的等式有效;

    这个怎么理解呢?

    在这里插入图片描述

    注意,

    • 上图选取是长度为8的数组
    • index[0,7]
    • 上图中tail的值已为公式tail=(tail+1)中+1后的tail值

    我们来看一个源码

       public void addLast(E e) {
            if (e == null)
                throw new NullPointerException();
            elements[tail] = e;
            if ( (tail = (tail + 1) & (elements.length - 1)) == head)
                doubleCapacity();
        }  

    在这个方法中,tail值的+1是放在if判断中的,并没有单独写tail++ 这样的语句,而不管if条件是否成立,tail的值都是会+1,而我上图中的标注的tail值是已+1过的。

    情况一,数组未出列,往数组里添加8个元素后,tail值每次+1,最后值为8
    代入公式中(tail = (tail + 1) & (elements.length - 1)) == head
    为(8 & 7)==0 转为二进制为(1000 & 111 )==0 000 结果返回true.

    情况二,数组出列2个,往数里面加了10个数据,tail值变为10.代入公式为 (10 & 7)==2 转为二进制为(1010 & 111)=10 ,返回true.

    可见,上述公式是在数组为2的n次长度时,是成立的。但如果是非2的n次方容量呢,还成立吗?

    举例
    在这里插入图片描述

    代入公式为 (7 & 6)==0 转为二进制为 ((111 & 110)=110 )≠ 000

    当然上面的例子你也可以再多找几个,你会发现真的只有当数组容量为2的n次方时,jdk提供的等式是成立的。

    另外,ArrayDeque提供了可以指定数组容量的构造器,那我输入非2的n次方数值,内部会发生什么?


    private void allocateElements(int numElements) {
            elements = new Object[calculateSize(numElements)];
        }
    private static int calculateSize(int numElements) {
            int initialCapacity = MIN_INITIAL_CAPACITY;
         
            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;
        }  

    它的内部是通过位移动算符 >>> 和 位运算符 | 实现的
    | = 这样的符号表示右边的值和左边的值 通过 位运算符 | 操作后赋值给左边的值。
    通过一系列的运算符操作,随机输入的数值,最后转换成比原数值大,且和它最近的2的n次方数值。

    费话不说, 我们来举例 传入 12

    在这里插入图片描述

    真是妙哉!


    ArrayDeque 既可实现普通队列 FIFO 先进先出,也可实现栈的先进后出功能

    其实也好理解,因为ArrayDeque实现了双端的操作 所以使得这一切都成为了可能

    • 先进先出
      addFirst() 方法 配合pollLast() 方法
      addLast() 方法 配合 pollFirst()方法

    • 先进后出(栈)
      addFirst() 方法配合 pollFirst()方法
      addLast()方法配合pollLast()方法


    Throws exception 和 return Special value

    特别注意,在操作数组时,有些方法遇问题,会直接抛出异常;有些方法,会反回Special value 也就是null值

    在这里插入图片描述

    更多简析思路,可参考以下博文

    Java 容器源码分析之 Deque 与 ArrayDeque

    Java进阶–ArrayDeque双端队列完全解析
    End!

  • 相关阅读:
    如何在仪表板中播放视频
    根据条件显示表格背景色
    如何个性化设置柱形图(条形图)柱子粗细、圆角和颜色?
    如何设置报表参数的日期区间?
    妙用这3个函数,实现各种你意想不到的条件格式化
    利用组合图对数据按百分比区间进行对比展示的实例
    报表传参跳转到仪表板的实现过程
    Wyn仪表板中的图表,如何针对每个字段设置自定义颜色
    Wyn仪表板中设置完整数据格式的方法
    Wyn门户及仪表板颜色设置方式汇总
  • 原文地址:https://www.cnblogs.com/xuwc/p/13936335.html
Copyright © 2011-2022 走看看