Deque
ArrayDeque
-
栈和队列
Deque的含义是“double ended queue”,即双端队列,它既可以当作栈使⽤,也可以当作队列使⽤。下表列出了Deque与Queue相对应的接⼝:
Queue Method Equivalent Deque Method 说明
add(e) addLast(e) 向队尾插⼊元素,失败则抛出异常
offer(e) offerLast(e) 向队尾插⼊元素,失败则返回 false
remove() removeFirst() 获取并删除队⾸元素,失败则抛出异常
poll() pollFirst() 获取并删除队⾸元素,失败则返回 null
element() getFirst() 获取但不删除队⾸元素,失败则抛出异常
peek() peekFirst() 获取但不删除队⾸元素,失败则返回 null
Stack Method Equivalent Deque Method 说明
push(e) addFirst(e) 向栈顶插⼊元素,失败则抛出异常
⽆ offerFirst(e) 向栈顶插⼊元素,失败则返回 false
pop() removeFirst() 获取并删除栈顶元素,失败则抛出异常
⽆ pollFirst() 获取并删除栈顶元素,失败则返回 null
peek() getFirst() 获取但不删除栈顶元素,失败则抛出异常
⽆ peekFirst() 获取但不删除栈顶元素,失败则返回 null -
ArrayDeque是⾮线程安全的(not thread-safe),当多个线程同时使⽤的时候,需要⼿动同步;另外,该容器不允许放⼊ null 元素。
-
add
head head 指向⾸端第⼀个有效元素, tail tail 指向尾端第⼀个可以插⼊元素的空
位。因为是循环数组,所以 head 不⼀定总等于 0, tail 也不⼀定总是⽐ head ⼤。
-
doubleCapacity()
-
总结
当需要实现先进先出(FIFO)或者先进后出(LIFO)的数据结构时,可以考虑使⽤ ArrayDeque。以下是⼀些使⽤ ArrayDeque 的场景:
管理任务队列:如果需要实现⼀个任务队列,可以使⽤ ArrayDeque 来存储任务元素。在队列头部添加新任务元素,从队列尾部取出任务进⾏处理,可以保证任务按照先进先出的顺序执⾏。
实现栈:ArrayDeque 可以作为栈的实现⽅式,⽀持 push、pop、peek 等操作,可以⽤于需要后进先出的场景。
实现缓存:在需要缓存⼀定数ᰁ的数据时,可以使⽤ ArrayDeque。当缓存的数据超过容量时,可以从队列头部删除最⽼的数据,从队列尾部添加新的数据。
实现事件处理器:ArrayDeque 可以作为事件处理器的实现⽅式,⽀持从队列头部获取事件进⾏处理,从队列尾部添加新的事件。
ArrayDeque 是 Java 标准库中的⼀种双端队列实现,底层基于数组实现。与 LinkedList 相⽐,ArrayDeque 的性能更优,因为它使⽤连续的内存空间存储元素,可以更好地利⽤ CPU 缓存,在⼤多数情况下也更快。
因为ArrayDeque 的底层实现是数组,⽽ LinkedList 的底层实现是链表。数组是⼀段连续的内存空间,⽽链表是由多个节点组成的,每个节点存储数据和指向下⼀个节点的指针。因此,在使⽤LinkedList 时,需要频繁进⾏内存分配和释放,⽽ ArrayDeque 在创建时就⼀次性分配了连续的内存空间,不需要频繁进⾏内存分配和释放,这样可以更好地利⽤ CPU 缓存,提⾼访问效率。
现代计算机CPU对于数据的局部性有很强的依赖,如果需要访问的数据在内存中是连续存储的,那么就可以利⽤CPU的缓存机制,提⾼访问效率。⽽当数据存储在不同的内存块⾥时,每次访问都需要从内存中读取,效率会受到影响。
当然了,使⽤ ArrayDeque 时,数组复制操作也是需要考虑的性能消耗之⼀。
当 ArrayDeque 的元素数量超过了初始容量时,会触发扩容操作。扩容操作会创建⼀个新的数组,并将原有元素复制到新数组中。扩容操作的时间复杂度为 O(n)。不过,ArrayDeque 的扩容策略(当 ArrayDeque 中的元素数ᰁ达到数组容ᰁ时,就需要进⾏扩容操作,扩容时会将数组容ᰁ扩⼤为原来的两倍)可以在⼀定程度上减少数组复制的次数和时间消耗,同时保证 ArrayDeque 的性能和空间利⽤率。
ArrayDeque 不仅⽀持常⻅的队列操作,如添加元素、删除元素、获取队列头部元素、获取队列尾部元素等。同时,它还⽀持栈操作,如 push、pop、peek 等。这使得 ArrayDeque 成为⼀种⾮常灵活的数据结构,可以⽤于各种场景的数据存储和处理。
PriorityDeque
在 PriorityQueue 中,每个元素都有⼀个优先级,这个优先级决定了元素在队列中的位置。队列内部通过⼩顶堆(也可以是⼤顶堆)的⽅式来维护元素的优先级关系。
具体来说,⼩顶堆是⼀个完全⼆叉树,任何⼀个⾮叶⼦节点的权值,都不⼤于其左右⼦节点的权值,这样保证了队列的顶部元素(堆顶)⼀定是优先级最⾼的元素。
完全⼆叉树(Complete Binary Tree)是⼀种⼆叉树,其中除了最后⼀层,其他层的节点数都是满的,最后⼀层的节点都靠左对⻬.
堆是⼀种完全⼆叉树,堆的特点是根节点的值最⼩(⼩顶堆)或最⼤(⼤顶堆),并且任意⾮根节点i的值都不⼤于(或不⼩于)其⽗节点的值。
- 节点关系
- 添加元素
调整的过程为:从 kk 指定的位置开始,将 xx 逐层与当前点的 parent parent 进⾏⽐较并交换,直到满⾜ x >= queue[parent] x >= queue[parent] 为⽌。注意这⾥的⽐较可以是元素的⾃然顺序,也可以是依靠⽐较器的顺序 - element()和 peek()
- remove()和 poll()
remove(Object o) 可以分为 2 种情况:
- 删除的是最后⼀个元素。直接删除即可,不需要调整。
- 删除的不是最后⼀个元素,从删除点开始以最后⼀个元素为参照调⽤⼀次 siftDown() 即可
- 总结
PriorityQueue 是⼀个⾮常常⽤的数据结构,它是⼀种特殊的堆(Heap)实现,可以⽤来⾼效地维护⼀个有序的集合。
它的底层实现是⼀个数组,通过堆的性质来维护元素的顺序。
取出元素时按照优先级顺序(从⼩到⼤或者从⼤到⼩)进⾏取出。
如果需要指定排序,元素必须实现 Comparable 接⼝或者传⼊⼀个 Comparator 来进⾏⽐较。