1、Deque-接口包含方法-简介
public interface Deque<E> extends Queue<E> { // 添加元素到队列头 void addFirst(E e); // 添加元素到队列尾 void addLast(E e); // 添加元素到队列头 boolean offerFirst(E e); // 添加元素到队列尾 boolean offerLast(E e); // 从队列头移除元素 E removeFirst(); // 从队列尾移除元素 E removeLast(); // 从队列头移除元素 E pollFirst(); // 从队列尾移除元素 E pollLast(); // 查看队列头元素 E getFirst(); // 查看队列尾元素 E getLast(); // 查看队列头元素 E peekFirst(); // 查看队列尾元素 E peekLast(); // 从队列头向后遍历移除指定元素 boolean removeFirstOccurrence(Object o); // 从队列尾向前遍历移除指定元素 boolean removeLastOccurrence(Object o); // *** 队列中的方法 *** // 添加元素,等于addLast(e) boolean add(E e); // 添加元素,等于offerLast(e) boolean offer(E e); // 移除元素,等于removeFirst() E remove(); // 移除元素,等于pollFirst() E poll(); // 查看元素,等于getFirst() E element(); // 查看元素,等于peekFirst() E peek(); // *** 栈方法 *** // 入栈,等于addFirst(e) void push(E e); // 出栈,等于removeFirst() E pop(); // *** Collection中的方法 *** // 删除指定元素,等于removeFirstOccurrence(o) boolean remove(Object o); // 检查是否包含某个元素 boolean contains(Object o); // 元素个数 public int size(); // 迭代器 Iterator<E> iterator(); // 反向迭代器 Iterator<E> descendingIterator();}
2、ArrayDeque-底层数据结构
1、ArrayDeque底层使用了数组来存储数据,同时用两个int值head和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;
3、ArrayDeque-构造方法
1、ArrayDeque有三个构造函数来初始化,除了无参的构造函数使用了默认容量,其它两个构造函数会通过allocateElements来计算初始容量。2、源码: public ArrayDeque() { elements = (E[]) new Object[16]; // 默认的数组长度大小 } public ArrayDeque(int numElements) { allocateElements(numElements); // 需要的数组长度大小,指定元素个数初始化 } public ArrayDeque(Collection<? extends E> c) { allocateElements(c.size()); // 根据集合来分配数组大小 addAll(c); // 把集合中元素放到数组中 }
4、ArrayDeque-分配数组大小
1、ArrayDeque 对数组的大小(即队列的容量)有特殊的要求,必须是 2^n[!!!]。2、源码: //初始化数组,计算出大于var1的最接近的2的n次方且大于等于8 //比如:3-->8、9-->16 //对于一个小于2^30的值,经过五次右移和位或操作后,可以得到一个2^k - 1的值。最后再将这个值+1,得到2^k。 //如果传入的值大于等于2^30,那么经过转化会变成负值,即< 0,此时会把初始值设置为2^30,即最大的容量只有2^30。 //无符号右移再进行按位或操作,就是将其低位全部补成1,然后再自加加一次,就是再向前进一位。 //这样就能得到其最小的2次幂。之所以需要最多移16位,是为了能够处理大于2^16次方数。 private void allocateElements(int var1) { int var2 = 8; if (var1 >= var2) { // 5次位移可以得到2的n次幂减1 var2 = var1 | var1 >>> 1; var2 |= var2 >>> 2; var2 |= var2 >>> 4; var2 |= var2 >>> 8; var2 |= var2 >>> 16; ++var2; // 传2的n次幂会有问题,最终的值是2的n+1次幂 if (var2 < 0) { var2 >>>= 1; } } this.elements = (Object[])(new Object[var2]); }
5、ArrayDeque-扩容
1、扩容:无论是从头部还是从尾部添加元素,都会判断tail==head,如果两个索引相遇,说明数组空间已满,需要扩容操作。2、源码: private void doubleCapacity() { assert this.head == this.tail; //扩容时头部索引和尾部索引肯定相等 int var1 = this.head; //头指针的位置 int var2 = this.elements.length; //旧数组的长度 int var3 = var2 - var1; //旧数组中,头指针离数组尾部的距离 int var4 = var2 << 1; //左移一位,相当于*2,即把新数组长度变为原来的两倍 //判断数组的长度是否小于0,也是判断是否有溢出 if (var4 < 0) { throw new IllegalStateException("Sorry, deque too big"); } else { Object[] var5 = new Object[var4]; //新建一个数组,长度为旧长度的两倍 System.arraycopy(this.elements, var1, var5, 0, var3);//将旧数组head之后的元素拷贝到新数组中 System.arraycopy(this.elements, 0, var5, var3, var1);//将旧数组下标0到head之间的元素拷贝到新数组中 this.elements = (Object[])var5;// 赋值为新数组 // head指向0,tail指向旧数组长度表示的位置 this.head = 0; this.tail = var2; } } // 拷贝该数组的所有元素到目标数组 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-1、ArrayDeque-扩容-源码分析-示意图
6、ArrayDeque-添加元素
1、添加元素,此处也是指入队,主要有两种方式:从队列头部、从队列尾部、2、入队的方法,有很多,此处分析,addFirst(E e)和addLast(E e) 2-1、.addFirst(E e)源码:在head的前面插入元素,在空间足够且下标没有越界的情况下,只需要将elements[--head] = e //将元素从数组的最后一个位置向前依次存放。 public void addFirst(E var1) { if (var1 == null) { throw new NullPointerException(); } else { /** 1、将head指针减1并与数组长度减1取模,防止数组到头了边界溢出,如果到头了就从尾再向前。 2、elements.length一定是2的n次幂,转换为二进制就是n+1位为1,其他位为0。 [比如:4 = 2^2(10) = 100(2)] 3、那么elements.length-1,从右到左n位都是1,比如4(10)=100(2)-1=011(2)。 4、第一次添加时,head=0,head-1=-1(10)=[00000001]原=[11111110]反 5、举例:elements.length=16,那么第一次添加:this.head = this.head - 1 & this.elements.length - 1 = 11111110 & 01111000 ------------ 01111000 ==> 15 第二次添加时head=15 head - 1 = 14 转为二进制 1110,1110 & 1111结果为1110(14) 6、可推出:实际上head = (head - 1) & (elements.length - 1)就是最后一位开始递减 */ //从最后一位开始依次向前添加元素 //x & (len - 1) = x % len,使用&的方式更快; //相当于取余,同时解决了head为负值的情况 this.elements[this.head = this.head - 1 & this.elements.length - 1] = var1; // 当head==tail时进行扩容 if (this.head == this.tail) { this.doubleCapacity(); } } } 2-2、.addLast(E e)源码:在tail的位置插入元素,由于tail总是指向下一个可以插入的空位,因此只需要elements[tail] = e; //将元素从数组的第一个位置依次向后存放。 public void addLast(E var1) { if (var1 == null) { throw new NullPointerException(); } else { //tail指针指向的是队列最后一个元素的下一个位置,在尾指针的位置放入元素。 //是把元素放在tail位置之后再把tail+1的,也就是说tail下标位置是没有值的,也就是含头不含尾。 this.elements[this.tail] = var1; //为了处理临界情况 (tail为length - 1时),和length - 1进行与操作,结果为0。 //比如:elements.length = 16,当head为0,tail当前值为15时实际上数组这时已经满了,那么tail + 1 = 16, elements.length - 1 = 15, head = 0,那么10000 & 1111 = 00000000 == head //x & (len - 1) = x % len,使用&的方式更快; if ((this.tail = this.tail + 1 & this.elements.length - 1) == this.head) { this.doubleCapacity(); } } } 2-3、 public boolean offerFirst(E e) { addFirst(e); return true; } 2-4、 public boolean offerLast(E e) { addLast(e); return true; }
6-1、ArrayDeque-头部添加元素-示意图
6-2、ArrayDeque-尾部添加元素-示意图


7、ArrayDeque-取出元素
1、取出元素,方法有很多,此处只介绍.pollFirst()和.pollLast()。 2、源码分析: //删除并返回Deque首端元素,也即是head位置处的元素。 public E pollFirst() { int var1 = this.head; //取出队列头元素 Object var2 = this.elements[var1]; if (var2 == null) { return null; } else { this.elements[var1] = null;//将队列头置为空 // 队列头指针右移一位,即头指针加1再和数组最大下标按位与计算出新的head this.head = var1 + 1 & this.elements.length - 1; return var2; } } //删除并返回Deque尾端元素,也即是tail位置前面的那个元素。 //注意:出队之后没有缩容 //每次pollLast()前tail先减1,然后再删除,tail指向的位置在元素的上一个位置。 //pollLast() 也是绕着数组循环删除的。tail一直绕着数组循环转动。 public E pollLast() { //尾指针左移一位, 取模的方式让头尾指针在数组范围内循环 int var1 = this.tail - 1 & this.elements.length - 1; //取当前尾指针处元素 Object var2 = this.elements[var1]; if (var2 == null) { return null; } else { // 将当前尾指针处置为空 this.elements[var1] = null; // tail指向新的尾指针处,将取出到的元素位置索引赋给tail,tail处值为null,含头不含尾 this.tail = var1; // 返回取得的元素 return var2; } } //返回但不删除Deque首端元素,也即是head位置处的元素,直接返回elements[head]即可。 public E peekFirst() { return this.elements[this.head]; } //返回但不删除Deque尾端元素,也即是tail位置前面的那个元素。 public E peekLast() { return this.elements[this.tail - 1 & this.elements.length - 1]; }
8、ArrayDeque-删除元素
1、删除元素 删除首尾元素2、源码: //removeFirst() 就是调用pollFirst() 不同之处在于在当pollFirst()返回null removeFirst() 抛出 null 元素异常。 public E removeFirst() { Object var1 = this.pollFirst(); if (var1 == null) { throw new NoSuchElementException(); } else { return var1; } } //removeLast() 就是调用pollLast() 不同之处在于在当pollLast()返回null removeLast() 抛出 null 元素异常。 public E removeLast() { Object var1 = this.pollLast(); if (var1 == null) { throw new NoSuchElementException(); } else { return var1; } } public boolean removeFirstOccurrence(Object var1) { if (var1 == null) { return false; } else { int var2 = this.elements.length - 1; Object var4; for(int var3 = this.head; (var4 = this.elements[var3]) != null; var3 = var3 + 1 & var2) { if (var1.equals(var4)) { this.delete(var3); return true; } } return false; } } public boolean removeLastOccurrence(Object var1) { if (var1 == null) { return false; } else { int var2 = this.elements.length - 1; Object var4; for(int var3 = this.tail - 1 & var2; (var4 = this.elements[var3]) != null; var3 = var3 - 1 & var2) { if (var1.equals(var4)) { this.delete(var3); return true; } } return false; } }
9、ArrayDeque-栈|队列-操作方法
1、栈-操作 public void push(E e) { addFirst(e); } public E pop() { return removeFirst(); }2、队列操作 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(); }
10、ArrayDeque-获取元素
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)]; }
11、ArrayDeque-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(); } }
https://blog.csdn.net/ztx114/article/details/89712772https://www.cnblogs.com/chenglc/p/10722304.htmlhttps://www.pdai.tech/md/java/collection/java-collection-Queue&Stack.htmlhttps://www.cnblogs.com/lxyit/p/9080590.html