您的位置:首页 > 产品设计 > UI/UE

JAVA Queue源码分析 java1.8

2016-08-09 17:50 232 查看

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元素
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 数据结构