Queue的传承

1.  Queue传承后代介绍java

Queue:  Deque :  ArrayDeque数组

                              LinkedList数据结构

               AbstractQueue :  PriorityQueue性能

注:橙色为继承,黑色为实现。测试

Queue队列,最明显的特征是:先进先出。该接口提供了6个接口,分为三组:spa

(1) add 和 offer : 队尾添加元素code

(2) remove 和 poll: 移除 队列的头对象

(3) element 和 peek: 获取队列的头,但不移除继承

很明显,这三对的add,remove,element继承Collection的, 后面三个才是Queue新增的接口。队列的三个接口当遇到超出边界时,不会抛出异常,要么返回false,要么返回null;而Collection的三个接口会抛出异常。索引

2.  Deque接口详情

Deque打破了队列的限制,能够在队尾队头实现元素的添加、移除、获取。

(1) addFirst / offerFirst  和 addLast / offerLast: 添加元素

(2) removeFirst / pollFirst 和 removeLast / pollLast: 移除元素

(3) getFirst / peekFirst 和 getLast / peekLast: 获取队列的头,但不移除

3.  ArrayDeque具体实现

要弄清楚ArrayDeque具体实现,则只需研究它的存储结构和元素存储方式索引便可。下面是关键变量:

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

    transient Object[] elements; // non-private to simplify nested class access

    transient int head;          

    transient int tail;

    // ...

}

由上面源码可知,这里的存储结构是数组。说实话很容易想固然,一个数组末端是队头,开端是队尾,正常队列添加元素的位置是队尾,也就是说数组,每添加一个元素,全队向后移1位;而后考虑队头出队列...每添加新元素,队头索引+1,出队列减1....每添加1个元素还需从新拷贝到新数组...等等,仔细想来这也太不靠谱了吧。其实,咱们无需关注队列元素实际是怎么存储的,只需保证出队列的顺序正确就能够;换一句话说,数组里面无需保证队尾和对头的位置在两端,他们甚至能够在任何一位置。

ArrayDeque的添加以及索引方式:

(1) 熟悉一下位运算

// 00000001 & 00000010 = 0
System.out.println( 1 & 2);

// 11111111 & 00000010 = 2
System.out.println( -1 & 2)

// 结论: 当 y>=0 时, 有 x & y <= y 成立

在这里咱们注意到: 利用 & 运算能够进行边界控制,数组无需进行边界检查,提升性能(一样能够用于集合)。

(2) 添加元素

public void addFirst(E e) {   // 在队头上添加元素
    if (e == null)
        throw new NullPointerException();
    elements[head = (head - 1) & (elements.length - 1)] = e;
        if (head == tail)
            doubleCapacity();
}


public void addLast(E e) {    // 在队尾上添加元素
    if (e == null)
        throw new NullPointerException();
    elements[tail] = e;
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)
            doubleCapacity();
}

首先测试一下:

String[] array = new String[4];  //数组
int h = 0;    // head
int t = 0;    // tail

h = ((h - 1) & (array.length - 1));            // h = 3
h = ((h - 1) & (array.length - 1));            // h = 2
h = ((h - 1) & (array.length - 1));            // h = 1
h = ((h - 1) & (array.length - 1));            // h = 0

h = ((h - 1) & (array.length - 1));            // h = 3
h = ((h - 1) & (array.length - 1));            // h = 2


t = ((t + 1) & (array.length - 1));            // t = 1
t = ((t + 1) & (array.length - 1));            // t = 2
t = ((t + 1) & (array.length - 1));            // t = 3

t = ((t + 1) & (array.length - 1));            // t = 0
t = ((t + 1) & (array.length - 1));            // t = 1
t = ((t + 1) & (array.length - 1));            // t = 2

进一步证实上面的结论。

(3) 当 t = h时,队列满了,则能够扩容了:

private void doubleCapacity() {
    assert head == tail;
    int p = head;
    int n = elements.length;
    int r = n - p;                // 头下标索引到数组末端的元素个数 
    int newCapacity = n << 1;     // 左移,扩大两倍
    if (newCapacity < 0)
        throw new IllegalStateException("Sorry, deque too big");
    Object[] a = new Object[newCapacity];

    // Object src, int srcPos, Object dest, int destPos, int length
    System.arraycopy(elements, p, a, 0, r);  // 旧数组中头右边的元素(包括头)拷贝到从下标0开始的新数组中
    System.arraycopy(elements, 0, a, r, p);  // 旧数组开始到头前面的元素拷贝到从r下标开始的新数组中

    elements = a;                     

    head = 0;     // 头下标变为0      这和刚开始添加元素的head是同样的,下一次队头的位置是 新数组末端
    tail = n;     // 尾下标变为旧数组的长度    
}

(4) 存储特色(出队列)

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);   // 头索引变化,添加时是减1,移除天然是加1
    return result;
}

java_queue_001

看上图,第1次在队头添加元素,索引为10;第2次在队头添加元素,索引为9,可见队头的位置并非在数组的末端,而是一直在变。同理,队尾也是如此。咱们只需记住位置索引,保证出队列的顺序就能够了(这一点经过上面&运算实现)。

4. PriorityQueue队列

PriorityQueue 里面存放的元素不能为null,且要求是能做比较的元素(即实现了 Comparator 接口);最小的元素在队头,能够存在相等的元素;固然因此该类的构造方法中,能够传递一个Comparator进去。它的数据结构叫堆(虽然容器是数组),在这里是最小堆,也就是堆的最顶端是最小的。

该堆实现一个二叉树,这棵二叉树的根节点是queue[0],左子节点是queue[1],右子节点是queue[2],而queue[3]又是queue[1]的左子节点,依此类推,给定元素queue[i],该节点的父节点是queue[(i-1)/2] 。因此给定位置k,它的父节点是:(k-1)/2,即(k-1)<<<2。

public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    modCount++;
    int i = size;               // 添加新元素的节点老是在数组的末端
    if (i >= queue.length)      // 扩容
        grow(i + 1);
    size = i + 1;
    if (i == 0)                 // 队列插入第一个元素的节点,无需比较大小
        queue[0] = e;
    else
        siftUp(i, e);           // 调整堆  拿父节点和新插入元素节点比较,若父节点值大于新元素,则交换。至关于提高位置
    return true;
}

/**
 * @param k the position to fill
 * @param x the item to insert
 */
private void siftUp(int k, E x) {
    if (comparator != null)
        siftUpUsingComparator(k, x);    // 使用构造传递进来的Comparator对象
    else
        siftUpComparable(k, x);         // 使用元素对象实现的Comparator比较方法
}

private void siftUpUsingComparator(int k, E x) {
    while (k > 0) {
        int parent = (k - 1) >>> 1;     // 无符号右移, 拿到即将插入元素节点对于的父节点位置
        Object e = queue[parent];
        if (comparator.compare(x, (E) e) >= 0)   // 若是新元素节点的值大于父节点的,则无需调整,直接赋值
            break;
        queue[k] = e;                   // 不然,新元素节点和父节点交换位置
        k = parent;
    }
    queue[k] = x;
}
相关文章
相关标签/搜索