【搞定Java集合框架】第3篇:LinkedList、Queue

本文部分内容来自:Java集合详解2:LinkedList和Queue,大部分内容为个人原创。

本文主要通过源码分析 LinkedList 和 Queue 这两种集合。

本文目录:

1、LinkedList

1.1  概述 

1.2  增删改查

2、Queue

2.1、DeQueue 接口

2.2  ArrayDeque 实现类

2.3  PriorityQueue 实现类

2.4  总结和同步的问题


1、LinkedList

  • LinkedList 的类结构
public class LinkedList<E>
	extends AbstractSequentialList<E>
	implements List<E>, Deque<E>, Cloneable, java.io.Serializable{

	// 元素个数
	transient int size = 0;

	// 头指针
	transient Node<E> first;

	// 尾指针
	transient Node<E> last;

	// 自定义迭代器 next
	private class ListItr implements ListIterator<E> {
		//...
		private int expectedModCount = modCount;
	}

	// 节点内部类:有前、后两个指针
	private static class Node<E> {
		E item;
		Node<E> next;
		Node<E> prev;

		Node(Node<E> prev, E element, Node<E> next) {
			this.item = element;
			this.next = next;
			this.prev = prev;
		}
	}

	// 从后往前遍历的迭代器 previous
	private class DescendingIterator implements Iterator<E> {
		// ...
	}
}
  • LinkedList 的构造函数
// 构造函数1
public LinkedList() {
}

// 构造函数2
public LinkedList(Collection<? extends E> c) {
	this();
	addAll(c);
}

1.1  概述 

LinkedList 与 ArrayList 一样实现 List 接口,只是 ArrayList 是 List 接口的大小可变数组的实现,LinkedList 是 List 接口链表的实现。基于链表实现的方式使得 LinkedList 在插入和删除时更优于 ArrayList,而随机访问则比 ArrayList 逊色些。

LinkedList 实现 List 接口所有可选的列表操作,并允许所有的元素包括 null。

除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。

此类还实现了 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。

所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。

同时,与ArrayList一样此实现不是同步的。

  • LinkedList 的定义
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{

}

从这段代码中我们可以清晰地看出 LinkedList 继承了 AbstractSequentialList 类,实现了 List、Deque、Cloneable、Serializable 接口。其中 AbstractSequentialList 提供了 List 接口的骨干实现,从而最大限度地减少了实现受“连续访问”数据存储(如链接列表)支持的此接口所需的工作,从而以减少实现List接口的复杂度。Deque 是一个线性的 Collection,支持在两端插入和移除元素,定义了双端队列的操作。

  • LinkedList 的属性

在LinkedList中提供了三个基本属性 size、first、last。

// 集合大小
transient int size = 0;

// 头指针
transient Node<E> first;

// 尾指针
transient Node<E> last;

LinkedList 的内部还有一个静态内部类:Node,是一个典型的双向链表的定义形式。

// 节点内部类:有前、后两个指针
private static class Node<E> {
	E item;         // 节点元素
	Node<E> next;   // 后继节点指针
	Node<E> prev;   // 前驱节点指针

	Node(Node<E> prev, E element, Node<E> next) {
		this.item = element;
		this.next = next;
		this.prev = prev;
	}
}

1.2  增删改查

  • 增加方法

LinkedList 中的添加操作很多,常用的几个方法如下:

方法1:add(E, e) 和 addLast:将指定元素添加到此链表的末尾处。

// 添加操作
public boolean add(E e) {
	linkLast(e);
	return true;
}


// 添加到尾节点
public void addLast(E e) {
	linkLast(e);
}

// 往链表的末尾添加一个元素
void linkLast(E e) {
	final Node<E> l = last;
	final Node<E> newNode = new Node<>(l, e, null);
	// 将新节点设置为尾节点
	last = newNode;
	if (l == null)
		// 如果原链表为空,则将头节点也设置成 newNode
		first = newNode;
	else
		// 否则将新结点设置为原尾节点的后继节点
		l.next = newNode;
	// 链表大小加1
	size++;
	// 链表的修改次数加1
	modCount++;
}

方法2:addFirst:将新元素添加到首节点处:

// 添加到首节点
public void addFirst(E e) {
	linkFirst(e);
}

// 将元素添加到首节点处
private void linkFirst(E e) {
	final Node<E> f = first;
	final Node<E> newNode = new Node<>(null, e, f);
	// 将头节点指针指向新结点
	first = newNode;
	if (f == null)
		// 如果链表为空,将尾指针也指向新插入的节点
		last = newNode;
	else
		// 否则,将头节点的前驱指针设置为新结点
		f.prev = newNode;
	// 集合元素大小加1
	size++;
	// 集合的修改次数加1
	modCount++;
}

方法3:插入指定的位置处:add(int  index, E element)

// 插入指定的位置处
public void add(int index, E element) {
        // 检查下标
	checkPositionIndex(index);

	if (index == size)
        // 如果 index 为当前链表尾节点,那么直接将新节点插入到尾节点处
		linkLast(element);
	else
        // 其实是插入到 index 位置处节点的前面
		linkBefore(element, node(index));
}

// 获取指定位置上的节点
Node<E> node(int index) {
	// assert isElementIndex(index);

	if (index < (size >> 1)) {
		Node<E> x = first;
		for (int i = 0; i < index; i++)
			x = x.next;
		return x;
	} else {
		Node<E> x = last;
		for (int i = size - 1; i > index; i--)
			x = x.prev;
		return x;
	}
}

// 将新节点插入到succ节点的前面
void linkBefore(E e, Node<E> succ) {
	// assert succ != null;
	final Node<E> pred = succ.prev;
	final Node<E> newNode = new Node<>(pred, e, succ);
	// 将新节点插入到index位置节点的前面
	succ.prev = newNode;
	if (pred == null)
		first = newNode;
	else
		pred.next = newNode;
	size++;
	modCount++;
}

方法4:一次添加多个节点,不再详解。

public boolean addAll(int index, Collection<? extends E> c) {  // ...  }

public boolean addAll(Collection<? extends E> c) { // ...}
  • 移除方法

方法1:remove(Object  o):移除链表中指定的元素

public boolean remove(Object o) {
	if (o == null) {
		for (Node<E> x = first; x != null; x = x.next) {
			if (x.item == null) {
				unlink(x);
				return true;
			}
		}
	} else {
		for (Node<E> x = first; x != null; x = x.next) {
			if (o.equals(x.item)) {
				unlink(x);
				return true;
			}
		}
	}
	return false;
}

该方法首先会判断移除的元素是否为null,然后迭代这个链表找到该元素节点,最后调用 unlink(Node<E> x),它是LinkedList 中所有移除方法的基础方法,如下:

E unlink(Node<E> x) {
	// 这个方法中的x不能为 null
	final E element = x.item;
	final Node<E> next = x.next;
	final Node<E> prev = x.prev;
	
	// 判断x节点是否为头节点
	if (prev == null) {
		first = next;
	} else {
		prev.next = next;
		x.prev = null;
	}

	// 判断x节点是否为尾节点
	if (next == null) {
		last = prev;
	} else {
		next.prev = prev;
		x.next = null;
	}
	
	// 将x节点的值设置为null
	x.item = null;
	// 集合元素大小减1
	size--;
	// 修改集合的次数加1
	modCount++;
	return element;
}

其他移除方法如下:

clear(): 从此列表中移除所有元素。

remove():获取并移除此列表的头(第一个元素)。

remove(int index):移除此列表中指定位置处的元素。

remove(Objec o):从此列表中移除首次出现的指定元素(如果存在)。

removeFirst():移除并返回此列表的第一个元素。

removeFirstOccurrence(Object o):从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。

removeLast():移除并返回此列表的最后一个元素。

removeLastOccurrence(Object o):从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表时)。
  • 查找方法

对于查找方法的源码就没有什么好介绍了,无非就是迭代,比对,然后就是返回当前值。

get(int index):返回此列表中指定位置处的元素。

getFirst():返回此列表的第一个元素。

getLast():返回此列表的最后一个元素。

indexOf(Object o):返回此列表中首次出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。

lastIndexOf(Object o):返回此列表中最后出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。
  • 修改方法
public E set(int index, E element) {
	// 检查index
	checkElementIndex(index);
	// 找出index处的节点
	Node<E> x = node(index);
	// 更新值
	E oldVal = x.item;
	x.item = element;
	// 返回旧值
	return oldVal;
}

2、Queue

  • Queue 接口的源码
public interface Queue<E> extends Collection<E> {
	
	//添加元素 
	boolean add(E e);

	// 添加元素
	boolean offer(E e);

	// 移除元素,队列为空时会抛出异常
	E remove();

	// 移除元素,队列为空时会返回null
	E poll();

	// 获取元素,队列为空时会抛出异常
	E element();

	// 获取元素,队列为空时会返回null
	E peek();
}

Queue接口定义了队列数据结构,元素是有序的(按插入顺序),先进先出。Queue接口相关的部分UML类图如下:

2.1、DeQueue 接口

package java.util;

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

	// *** Queue methods ***
	
	boolean add(E e);
	
	boolean offer(E e);
	
	E remove();
  
	E poll();
	
	E element();
   
	E peek();

	// *** Stack methods ***
	void push(E e);
   
	E pop();

	// *** Collection methods ***

	boolean remove(Object o);

	boolean contains(Object o);

	public int size();

	Iterator<E> iterator();

	Iterator<E> descendingIterator();
}

DeQueue(Double-ended queue)为接口,继承了Queue接口,创建双向队列,灵活性更强,可以前向或后向迭代,在队头队尾均可随心插入或删除元素。它的两个主要实现类是 ArrayDeque 和 LinkedList。

LinkedList 上面已经讲过了,下面就讲下 DeQueue 的另外一个实现类:ArrayDeque。

2.2  ArrayDeque 实现类

ArrayDeque 底层使用的是循环数组实现 “双端 / 双向队列”。

  • ArrayDeque 的类结构
public class ArrayDeque<E> extends AbstractCollection<E>
			   implements Deque<E>, Cloneable, Serializable{
	
	// 元素都保存在数组中
	private transient E[] elements;

	// 头索引下标
	private transient int head;

	// 尾索引下标
	private transient int tail;

	// 最小容量
	private static final int MIN_INITIAL_CAPACITY = 8;

	// 自定义迭代器:从前往后遍历
	private class DeqIterator implements Iterator<E> {
	
		private int cursor = head;
		private int fence = tail;
		
		// ...
	}
	
	// 自定义迭代器:从后往前遍历
	private class DescendingIterator implements Iterator<E> {
		
		private int cursor = tail;
		private int fence = head;
		
		// ...
	}
	
	// ...
}
  • ArrayDeque 的类构造器
// 构造器1:默认初始化大小为16
public ArrayDeque() {
	elements = (E[]) new Object[16];
}

// 构造器2:指定初始化大小
public ArrayDeque(int numElements) {
	allocateElements(numElements);
}

// 构造器3:根据集合c指定大小
public ArrayDeque(Collection<? extends E> c) {
	allocateElements(c.size());
	addAll(c);
}

可以看到除了默认构造器外,其他两种指定大小的构造器都是调用 allocateElements(int  numElements) 方法完成的:

private void allocateElements(int numElements) {
	// 最小容量为8
	int initialCapacity = MIN_INITIAL_CAPACITY;
	// 如果要分配的容量大于等于8,扩大成2的幂(是为了维护头、尾下标值);
	// 否则使用最小容量8
	if (numElements >= initialCapacity) {
		initialCapacity = numElements;
		initialCapacity |= (initialCapacity >>>  1);
		initialCapacity |= (initialCapacity >>>  2);
		initialCapacity |= (initialCapacity >>>  4);
		initialCapacity |= (initialCapacity >>>  8);
		initialCapacity |= (initialCapacity >>> 16);
		initialCapacity++;

		if (initialCapacity < 0)   // Too many elements, must back off
			initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements
	}
	elements = (E[]) new Object[initialCapacity];
}

可以看到容量的大小都是2的幂,这样是为了方便维护头、尾的下标。

  • 添加操作

ArrayDeque 的添加方法很多,具体的方法看上面的类结构所示。这里就简单分析下常用的几种添加元素的方法:

方法1:add(E  e) 方法:实际上调用的是addLast(E  e)方法,即将元素添加到数组的结尾处

public boolean add(E e) {
	// 调用addLast方法
	addLast(e);
	return true;
}

那下面就来看一看 addLast 方法:

public void addLast(E e) {
	if (e == null)
		// 如果e为空指针则抛出空指针异常
		throw new NullPointerException();
	// 将新元素放在tail下标处
	elements[tail] = e;
	if ( (tail = (tail + 1) & (elements.length - 1)) == head)
		// 扩容
		doubleCapacity();
}

这里看下 addLast 方法中的扩容时机:

if ( (tail = (tail + 1) & (elements.length - 1)) == head)
    // 扩容
    doubleCapacity();

尾索引+1 然后与数组的长度减1(elements - 1)进行“与运算”。因为 length 是2的幂,所以 (length - 1)转换为二进制后全是1。

所以如果尾索引值 tail 小于等于 (length - 1),那么“与运算”后仍为 tail 本身,如果刚好比(length - 1)大 1 时(即其二进制除了首位,其他位都是0),”与运算“后 tail 便为 0(即回到了初始位置)。

正是通过 (tail + 1) & (elements.length - 1)  这个“与运算”实现了数组的双向循环。

如果尾索引和头索引重合了,则说明数组已满,需要进行扩容,扩容为以前的2倍。

private void doubleCapacity() {
	// 先判断头节点是否和尾节点重合了
	assert head == tail;
	int p = head;
	int n = elements.length;
	int r = n - p; // number of elements to the right of p
	// 向左移动一位,即扩大一倍
	int newCapacity = n << 1;
	if (newCapacity < 0)
		throw new IllegalStateException("Sorry, deque too big");
	Object[] a = new Object[newCapacity];
	System.arraycopy(elements, p, a, 0, r);
	System.arraycopy(elements, 0, a, r, p);
	elements = (E[])a;
	head = 0;
	tail = n;
}

 方法2:addFirst(E  e) 的实现

public void addFirst(E e) {
	if (e == null)
		throw new NullPointerException();
	/** 此处如果head为0,则-1(1111 1111 1111 1111 1111 1111 1111 1111)
	与(length - 1)进行取‘&’运算,结果必然是(length - 1),
	即回到了数组的尾部。 */
	elements[head = (head - 1) & (elements.length - 1)] = e;
	// 如果尾索引和头索引重合了,说明数组满了,进行扩容
	if (head == tail)
		doubleCapacity();
}
  • 删除操作

remove() 相关的方法实际上调用的都是 poll() 相关方法去完成的:

public E remove() {
	// 调用 removeFirst方法
	return removeFirst();
}

public E removeFirst() {
	// 调用 pollFirst 方法
	E x = pollFirst();
	if (x == null)
		throw new NoSuchElementException();
	return x;
}

public E pollFirst() {
	// 将队首元素弹出
	int h = head;
	E result = elements[h]; // Element is null if deque empty
	if (result == null)
		return null;
	elements[h] = null;     // Must null out slot
	// 头索引加1
	head = (h + 1) & (elements.length - 1);
	return result;
}

除此之外,还有以下删除方法:

// 移除队尾元素
public E removeLast() {
	E x = pollLast();
	if (x == null)
		throw new NoSuchElementException();
	return x;
}

public E pollLast() {
	// 尾索引-1
	int t = (tail - 1) & (elements.length - 1);
	// 删除队尾元素
	E result = elements[t];
	if (result == null)
		return null;
	elements[t] = null;
	tail = t;
	return result;
}
  • 查找操作
// 获取元素:实际上调用的是getFirst()方法
public E element() {
	return getFirst();
}

// 获取队首元素
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)];
	if (x == null)
		throw new NoSuchElementException();
	return x;
}

2.3  PriorityQueue 实现类

PriorityQueue 底层是用数组实现堆的数据结构。

  • PriorityQueue 的类结构 
public class PriorityQueue<E> extends AbstractQueue<E>
	implements java.io.Serializable {

	// 默认的初始化大小是11
	private static final int DEFAULT_INITIAL_CAPACITY = 11;
	
	// 数据结构为:数组
	private transient Object[] queue;
	
	// 集合元素的大小
	private int size = 0;
	
	// 因为要用数组实现堆结构,所以需要使用 Comparator
	private final Comparator<? super E> comparator;
	
	// 记录 PriorityQueue 的修改次数
	private transient int modCount = 0;
	
	// 自定义迭代器
	 private final class Itr implements Iterator<E> { // ...}
	 
	// ... 省略
}
  • PriorityQueue 的构造函数

PriorityQueue 一共有6个构造器,只需要关注前三个就行了。

// 构造函数1:默认构造器
public PriorityQueue() {
	this(DEFAULT_INITIAL_CAPACITY, null);
}

// 构造器2:自定义大小
public PriorityQueue(int initialCapacity) {
	this(initialCapacity, null);
}

// 构造器3:实现堆结构
public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {
	
	if (initialCapacity < 1)
		throw new IllegalArgumentException();
	this.queue = new Object[initialCapacity];
	this.comparator = comparator;
}

// 构造器4
@SuppressWarnings("unchecked")
public PriorityQueue(Collection<? extends E> c) {
	if (c instanceof SortedSet<?>) {
		SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
		this.comparator = (Comparator<? super E>) ss.comparator();
		initElementsFromCollection(ss);
	}
	else if (c instanceof PriorityQueue<?>) {
		PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
		this.comparator = (Comparator<? super E>) pq.comparator();
		initFromPriorityQueue(pq);
	}
	else {
		this.comparator = null;
		initFromCollection(c);
	}
}

// 构造器5:
@SuppressWarnings("unchecked")
public PriorityQueue(PriorityQueue<? extends E> c) {
	this.comparator = (Comparator<? super E>) c.comparator();
	initFromPriorityQueue(c);
}

// 构造器6:
@SuppressWarnings("unchecked")
public PriorityQueue(SortedSet<? extends E> c) {
	this.comparator = (Comparator<? super E>) c.comparator();
	initElementsFromCollection(c);
}

优先队列跟普通的队列不一样,普通队列是一种遵循 FIFO 规则的队列,拿数据的时候按照加入队列的顺序拿取。 而优先队列每次拿数据的时候都会拿出优先级最高的数据。

优先队列内部维护着一个堆,每次取数据的时候都从堆顶拿数据(堆顶的优先级最高),这就是优先队列的原理。

  • 添加方法 / 入队方法

调整方法需要关注下,因为往堆里插入一个元素,是先插入到叶子节点,再从下往上进行调整,要理解这个过程的实现。

首先调用 add(E  e) 方法:

public boolean add(E e) {
	// 调用 offer 方法
	return offer(e);
}

调用 offer(E  e) 方法:

public boolean offer(E e) {
	if (e == null)
		throw new NullPointerException();
	// 集合操作次数加1
	modCount++;
	int i = size;
	// 如果当前用堆表示的数组已经满了,调用grow方法扩容
	if (i >= queue.length)
		grow(i + 1);
	size = i + 1;
	// 如果堆为空的时候
	if (i == 0)
		// 直接给堆顶元素赋值
		queue[0] = e;
	else
		// 堆中已有元素的情况
		// 重新调整堆,从下往上调整,因为新增元素是先加到最后一个叶子节点
		siftUp(i, e);
	return true;
}

下面就看看添加元素后,调整堆所用到的 siftUp(int  k , E  x) 的内部实现:

private void siftUp(int k, E x) {
	// 判断比较器是否存在
	if (comparator != null)
		// 使用比较器调整
		siftUpUsingComparator(k, x);
	else
		// 比较器不存在的情况下,使用元素自身的比较进行调整
		siftUpComparable(k, x);
}

再看看存在比较器时,调用的 siftUsingComparator(int  k,  E  x) 方法的实现:

private void siftUpUsingComparator(int k, E x) {
	// 一直循环直到父节点还存在
	while (k > 0) {
		// 找到父节点索引,等同于(k - 1)/ 2
		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;
}

  • 出队方法:poll

​​​​​​​删除元素:删除的是堆顶元素,删除后需要从上往下调整堆结构。

public E poll() {
	if (size == 0)
		return null;
	// 元素个数减1
	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;
}

下面就看下,从上往下调整的方法:siftDown(int  k,  E  x) 的内部实现:

private void siftDown(int k, E x) {
	// 比较器存在的情况下
	if (comparator != null)
		// 使用比较器调整
		siftDownUsingComparator(k, x);
	else
		// 比较器不存在的情况下,使用元素自身的比较调整
		siftDownComparable(k, x);
}

可以看到其内部调用了比较器方法:siftDownUsingComparator(int  k, E  x) :

private void siftDownUsingComparator(int k, E x) {
	// 只需循环节点个数的一半即可
	int half = size >>> 1;
	while (k < half) {
		// 得到父节点的左子节点索引,即(k * 2)+ 1
		int child = (k << 1) + 1;
		// 得到左子元素
		Object c = queue[child];
		// 得到父节点的右子节点索引
		int right = child + 1;
		// 左子节点跟右子节点比较,取更大的值
		if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)
			c = queue[child = right];
		// 然后这个更大的值跟最后一个叶子节点比较
		if (comparator.compare(x, (E) c) <= 0)
			break;
		// 新位置使用更大的值
		queue[k] = c;
		// 新位置索引变成子元素索引,进行递归操作
		k = child;
	}
	// 最后一个叶子节点添加到合适的位置
	queue[k] = x;
}

  • 删除队列元素:remove
public boolean remove(Object o) {
	// 找到 o 在队列中对应的索引
	int i = indexOf(o);
	// 不存在的话直接返回 false
	if (i == -1)
		return false;
	else {
		// 存在的话调用 removeAt 方法,并返回true
		removeAt(i);
		return true;
	}
}

那么下面就看下根据下标删除元素的方法:remove(int  i) 的内部实现:

private E removeAt(int i) {
	// 保证i的有效性
	assert i >= 0 && i < size;
	modCount++;
	// 元素个数减1
	int s = --size;
	// 如果是删除最后一个叶子节点
	if (s == i) 
		// 直接置空,删除即可,堆还是保持特质,不需要调整
		queue[i] = null;
	// 如果是删除的不是最后一个叶子节点
	else {
		// 获得最后1个叶子节点元素
		E moved = (E) queue[s];
		// 将最后1个叶子节点置空
		queue[s] = null;
		// 从上往下调整
		siftDown(i, moved);
		// 如果从上往下调整完毕之后发现元素位置没变,再从下往上调整
		if (queue[i] == moved) {
			// 从下往上调整
			siftUp(i, moved);
			if (queue[i] != moved)
				return moved;
		}
	}
	return null;
}

先执行 siftDown() 下滤的过程:

再执行 siftUp() 的过程:

2.4  总结和同步的问题

1、JDK 内置的优先队列PriorityQueue内部使用一个堆维护数据,每当有数据add进来或者poll出去的时候会对堆做从下往上的调整和从上往下的调整。

2、PriorityQueue不是一个线程安全的类,如果要在多线程环境下使用,可以使用 PriorityBlockingQueue 这个优先阻塞队列。其中add、poll、remove方法都使用 ReentrantLock 锁来保持同步,take() 方法中如果元素为空,则会一直保持阻塞。

 

猜你喜欢

转载自blog.csdn.net/pcwl1206/article/details/86487247