JAVA Queue源码分析 java1.8

来源:互联网 发布:淘宝汽车服务 编辑:程序博客网 时间:2024/05/22 05:29

JAVA Queue

第一篇博客,希望以后每天坚持
目录:
1.Queue接口
2.Java中Queue接口方法
3.Queue的子类PriorityQueue分析
4.总结


1.Queue接口
数据结构中的队列,先进先出式的数据结构。
主要注意的时,Java中的Queue是比数据结构中理解的Queue更加灵活。这表现在:
a.数据结构中的Queue是按时间顺序的先进先出,即你先插入的元素总是先出的。但是JAVA中的Queue确实运行你实现自己的策略,确定什么元素是“优先”的,如它的value值模拟“它的达到时间”,value越大,则优先级越高,越排在前面。当前你也可以按照自然的常理的时间顺序。(注意:你确定了你的策略之后,就不可变了,不能一会说按照时间顺序,一会按照value倒叙)
b.比通常的queue多了一个方法peek,你可以查看却不删除,根据当前的值,觉得你的操作。


2.Java中Queue接口方法
六个:
a:add(E e):添加一个元素
b:remove():删除一个元素
c:offer(E e):添加一个元素
d:poll(E e):删除一个元素
f: element() :查看最上一个元素
h:peek():查看最上一个元素
上面六个函数总体上分为两类:安全的会进行容量检查的(add,remove,element),如果队列没有值,则取元素会抛出IlleaglStatementException异常。不安全的不进行容量控制的(offer,poll,peek )。

既然是这样,为什么要有两条语句相同的一个安全、不个不安全的呢?只提供一套安全的不久可以了么?

我的理解:有时候需要通过抛出异常来判断是容器中包涵null还是没有值。Queue通常不允许插入null值元素。但是有的实现却是允许插入null值的,如LinkedList.
另外,这里的分类不是严格的,并不是说所有queue的子类的add方法都会进行容量检查,然后抛出异常。这是要看容器的特性的,如Priorityqueue是无界的,add方法是直接的调用的offer方法。


3.Queue的子类PriorityQueue分析
3.1主要的方法成员
private static final int DEFAULT_INITIAL_CAPACITY = 11;
transient Object[] queue; // non-private to simplify nested class access

/** * The number of elements in the priority queue. */private int size = 0;/** * The comparator, or null if priority queue uses elements' * natural ordering. */private final Comparator<? super E> comparator;/** * The number of times this priority queue has been * <i>structurally modified</i>.  See AbstractList for gory details. */transient int modCount = 0; // non-private to simplify nested class access

说明:PriorityQueue使用数组存储数据,基于数组形式的小根堆来做的。

3.2主要方法分析
a.构造方法

    public PriorityQueue(int initialCapacity,                     Comparator<? super E> comparator) {    // Note: This restriction of at least one is not actually needed,    // but continues for 1.5 compatibility    if (initialCapacity < 1)        throw new IllegalArgumentException();    //数组的大小,直接看用户指定的initialCapacity,没有指定就是默认值11          //区别于Map类型的,是2^n次方。    this.queue = new Object[initialCapacity];    this.comparator = comparator;}

注意:PriorityQueue是在构造函数调用阶段就已经申请了底层数组,而有的容器如HashMap是采用的懒加载机制,在实际的使用的时候,才会真正的去申请底层的数据空间(可能原因:hashmap是一个比较费空间的,因为来避免碰撞,获得较好的性能,则一般需要申请较大的底层数组,所以这种开销大的动作能推迟就推迟,而Queue在默认的没有指定初始容量的时候,只申请了长度为11的数组,开销较小。不过ArrayList在没有指定初始化容量的时候,也是采用的类似懒加载机制,指向一个长度为0的空数组,真正使用的时候才创建底层数组的空间,关于ArrayList其他的容器在以后再说)

b.添加元素
add方法:直接调用offer(E)方法
offer方法:
比较简单,因为priorityQueue是无界队列,所以,添加元素不会抛出illegalStateException异常。如果当前数组已满,则会直接扩容。
过程:
a:安全性检查,不运行插入null元素
b:容量检查,如果容量不够,则扩容。扩容原则:如果当前基层数组较小,则扩容时,每次扩展一倍。之后每次扩容时,每次扩展0.5倍
c.如果当前队列为空,则直接插入到queue[0]位置,不需要调整。否则,将元素直接“放到”一个有效位置上,然后调整,不断上浮。

    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;}

上浮函数:不断的为新节点找位置,最后找到位置后,才赋值一次。

  private void siftUpComparable(int k, E x) {//父亲节点N,孩子节点2*N+1,2(N+1),则孩子节点编号k,父亲节点为(k - 1) >>> 1。0位置//也是存数据的    Comparable<? super E> key = (Comparable<? super E>) x;    while (k > 0) {        int parent = (k - 1) >>> 1;        Object e = queue[parent];        if (key.compareTo((E) e) >= 0)            break;        queue[k] = e;        k = parent;    }    queue[k] = key;}

c.删除元素
remove函数:主要还是调用的poll函数,只是增加了异常处理(属于装饰器模式么?)

    public E remove() {    E x = poll();    if (x != null)        return x;    else        throw new NoSuchElementException();}

poll函数:
主要步骤:
a.容量检查
b.size标志减一,同时保存堆顶元素。
c.将最后一个元暂时提到堆顶位置,然后将堆顶元素调整,下移。

    public E poll() {    if (size == 0)        return null;    int s = --size;    modCount++;    E result = (E) queue[0];    E x = (E) queue[s];    queue[s] = null;    if (s != 0)        siftDown(0, x);    return result;}    private void siftDownComparable(int k, E x) {    Comparable<? super E> key = (Comparable<? super E>)x;    int half = size >>> 1;        // loop while a non-leaf    while (k < half)     {        int child = (k << 1) + 1; // assume left child is least        Object c = queue[child];        int right = child + 1;        //C为左右孩子中最小的        if (right < size &&            ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)            c = queue[child = right];        //如果key小于孩子,则不用调整的,否则,需要下移,孩子上移        if (key.compareTo((E) c) <= 0)            break;        //孩子上移        queue[k] = c;        k = child;    }    queue[k] = key;}

d.清除元素
特别要注意的地方:要清楚引用,不能只是将size置位0,这样的话会造成内存泄漏

    public void clear() {    modCount++;    //清楚引用,不能只是将size置位0,这样的话会造成内存泄漏    for (int i = 0; i < size; i++)        queue[i] = null;    size = 0;}

3.3其他
a.堆化函数
如果在构造PriorityQueue时,使用一个非priorityqueue集合初始queue,则策略是先将集合中的元素拷贝到底层的数组中,然后调用堆化函数调整元素顺序,使满足堆的性质。
过程:
调整非叶子点[0 , 2/size-1]之间的。

    private void heapify() {    for (int i = (size >>> 1) - 1; i >= 0; i--)        siftDown(i, (E) queue[i]);}

4.总结
a:priorityqueue是无界队列
b:和其他集合容器一样,构造时,尽量大概估计容器大小
c:不允许null元素

1 0
原创粉丝点击