• ArrayDeque解析


    Queue接口

    1 public abstract boolean add(E paramE);
    2 public abstract boolean offer(E paramE);  // 加入元素
    3 public abstract E remove();
    4 public abstract E poll(); // 获取元素

    Queue接口提供了以上几个方法。

    Queue使用时要尽量避免Collection的add()和remove()方法,而是要使用offer()来加入元素,使用poll()来获取并移出元素。

    它们的优点是通过返回值可以判断成功与否,add()和remove()方法在失败的时候会抛出异常。

    如果要使用前端而不移出该元素,使用element()或者peek()方法。

    ArrayDeque

    ArrayDeque的特点

    • 大小自增长的队列
    • 内部使用数组存储数据
    • 线程不安全
    • 内部数组长度为8、16、32….. 2的n次方
    • 头指针head从内部数组的末尾开始,尾指针tail从0开始,在头部插入数据时,head减一,在尾部插入数据时,tail加一。当head==tail时说明数组的容量满足不了当前的情况,此时需要扩大容量为原来的二倍。

    核心思想图

    image

    代码解析

    • 分配数组大小
     1 private void allocateElements(int numElements) {
     2         int initialCapacity = MIN_INITIAL_CAPACITY;
     3         // 如果指定的数组长度大于最小的值,需要计算数组大小。
     4         // 算法利用或运算和右移运算,计算结果始终为2的n次方,比如numElements的值为4、5、6、7时,initialCapacity的结果为8;numElements的值为8、9、10、11、12、13、14、15时,initialCapacity的结果为16。
     5         // 设计数组的长度为2的n次方是为循环链表考虑的,后面会解释。
     6         if (numElements >= initialCapacity) {
     7             initialCapacity = numElements;
     8             initialCapacity |= (initialCapacity >>>  1);
     9             initialCapacity |= (initialCapacity >>>  2);
    10             initialCapacity |= (initialCapacity >>>  4);
    11             initialCapacity |= (initialCapacity >>>  8);
    12             initialCapacity |= (initialCapacity >>> 16);
    13             initialCapacity++;
    14             // 如果值太大溢出(即值小于0),需要缩小2倍
    15             if (initialCapacity < 0)   // Too many elements, must back off
    16                 initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
    17         }
    18         elements = new Object[initialCapacity];
    19     }
    • 扩大数组长度
     1 private void doubleCapacity() {
     2         // assert head == tail;
     3         int p = head;
     4         int n = elements.length;
     5          // 头指针右边的长度
     6         int r = n - p;
     7         // 扩大2倍
     8         int newCapacity = n << 1;
     9         // 检测新的数组长度是否太大
    10         if (newCapacity < 0)
    11             throw new IllegalStateException("Sorry, deque too big");
    12         Object[] a = new Object[newCapacity];
    13         // 复制头指针右边的数据
    14         System.arraycopy(elements, p, a, 0, r);
    15         // 复制头指针左边的数据
    16         System.arraycopy(elements, 0, a, r, p);
    17         elements = a;
    18         // 初始化头指针为0
    19         head = 0;
    20         // 初始化尾指针为n
    21         tail = n;
    22     }
    • 在头部插入数据
     1 public void addFirst(E e) {
     2         if (e == null)
     3             throw new NullPointerException("e == null");
     4         // 在头部插入数据,头指针向左移动1,即减一,elements.length为2的n次方,所以elements.lenght-1的二进制表示为1111....1111。
     5         // 特殊情况:假设数组的长度为16,初始化时head为0,那么(head - 1) & (elements.length - 1)就是-1 & 15的值,负数的二进制为对应正数的二进制的补码,-1即为11111....1111,那么-1 & 15 = 15,如上图,当首次向队列头部插入数据时,head在数组的末尾。
     6         elements[head = (head - 1) & (elements.length - 1)] = e;
     7         // 头指针和尾指针相等时(即相遇),表示当前的数组长度不够,需要扩大数组长度
     8         if (head == tail)
     9             doubleCapacity();
    10     }
    • 在尾部插入数据
    1 public void addLast(E e) {
    2         if (e == null)
    3             throw new NullPointerException("e == null");
    4         elements[tail] = e;
    5         // tail向右移动,如果头指针和尾指针相等时(即相遇),表示当前的数组长度不够,需要扩大数组长度
    6         if ( (tail = (tail + 1) & (elements.length - 1)) == head)
    7             doubleCapacity();
    8     }
    • 遍历
     1 private class DeqIterator implements Iterator<E> {
     2         // 从头指针位置开始遍历
     3         private int cursor = head;
     4         private int fence = tail;
     5         private int lastRet = -1;
     6 
     7         public boolean hasNext() {
     8             return cursor != fence;
     9         }
    10 
    11         public E next() {
    12             if (cursor == fence)
    13                 throw new NoSuchElementException();
    14             @SuppressWarnings("unchecked") E result = (E) elements[cursor];
    15 
    16             if (tail != fence || result == null)
    17                 throw new ConcurrentModificationException();
    18             lastRet = cursor;
    19             // 每次加一,利用与运算,形成循环。比如,数组的长度为16,当cursor递增到15时,下面的计算应为16 & 15 = 0,cursor的值变为了0.
    20             cursor = (cursor + 1) & (elements.length - 1);
    21             return result;
    22         }
    23 
    24         public void remove() {
    25             if (lastRet < 0)
    26                 throw new IllegalStateException();
    27             if (delete(lastRet)) { // if left-shifted, undo increment in next()
    28                 cursor = (cursor - 1) & (elements.length - 1);
    29                 fence = tail;
    30             }
    31             lastRet = -1;
    32         }
    33     }

    有以下几点总结:

    1)ArrayDeque有两个类属性,head和tail,两个指针。

    2)ArrayDeque通过一个数组作为载体,其中的数组元素在add等方法执行时不移动,发生变化的只是head和tail指针,而且指针是循环变化,数组容量不限制。

    3)offer方法和add方法都是通过其中的addLast方法实现,每添加一个元素,就把元素加到数组的尾部,此时,head指针没有变化,而tail指针加一,因为指针是循环加的,

         所以当tail追上head((this.tail = this.tail + 1 & this.elements.length - 1) == this.head)时,数组容量翻一倍,继续执行。

    4)remove方法和poll方法都是通过其中的pollFirst方法实现,每移除一个元素,该元素所在位置变成null,此时,tail指针没有变化,而head指针加一,当数组中没有数据时,返回null。

    5)因为ArrayDeque不是线程安全的,所以,用作堆栈时快于 Stack,在用作队列时快于 LinkedList。

  • 相关阅读:
    NoSQL、memcached介绍、安装memcached、查看memcached状态
    报警系统配置文件
    shell中的函数、数组、报警系统脚本
    for循环、while循环、break、continue、exit
    Shell脚本中的逻辑判断、文件目录属性判断、if的特殊用法、case判断
    Shell脚本、Shell脚本结构、date命令的用法、变量
    zabbix的自动发现、自定义添加监控项目、配置邮件告警
    rabbitMQ中的Vhost理解、创建和使用
    charset编码问题:YAMLException: java.nio.charset.MalformedInputException
    java jna 报错:Unable to load library
  • 原文地址:https://www.cnblogs.com/wihainan/p/6305993.html
Copyright © 2020-2023  润新知