友情提示:文章可能有點長脚草,各種集合的實現(xiàn)原理我用自己的理解去解釋了,配合源碼食用更佳原献。
一馏慨、Java集合框架簡述
簡介:上古時期(Java 2之前) Java 就提供的數據操作類:Dictionary、Vector姑隅、Stack 等写隶,但是它們存在著效率較低、缺少統(tǒng)一的主題的問題讲仰。為了解決這些問題慕趴,Java 重新進行了設計,就是現(xiàn)在的 Java集合框架鄙陡。
從上圖可以看出冕房,Java 集合框架主要包含三個方面:
- 迭代器(Iterator):提供一種方法訪問一個容器(container)對象中各個元素,而又不需暴露該對象的內部細節(jié)柔吼。
- 集合(Collection):用來儲存元素集合毒费,列表等丙唧。
Collection 包括 List愈魏、Set 和 Queue。這些抽象是為了定義通用方法和變量想际,統(tǒng)一管理抽象和實現(xiàn)類等培漏。常用實現(xiàn)類有 HashSet、LinkedHashSet胡本、ArrayList 等牌柄。 - 圖(Map):用于儲存鍵值對。
Map 包括 AbstractMap 和 SortedMap 等抽象侧甫,原理與上面相同珊佣。再往下是 HashMap蹋宦、LinkedHashMap 等具體實現(xiàn)。
二咒锻、集合框架的構成
2.1 接口 Iterable
說明:
- 是 Java 集合框架的頂級接口冷冗。
- 實現(xiàn)此接口,定義自己的迭代器實現(xiàn)對數據容器的遍歷惑艇。
Iterable 三個成員方法:
Iterator<T> iterator(); 返回一個迭代器蒿辙。
void forEach(Consumer<? super T> action):對內部元素進行遍歷,并對元素進行指定的操作
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
pliterator<T> spliterator() :創(chuàng)建并返回一個可分割迭代器
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
那么回頭看第一個成員方法滨巴,返回的是一個迭代器思灌,這個迭代器也是一個接口,里面定義了常用的一些方法:
接口 Iterator :迭代器
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
- hasNext():是否存在下一個元素恭取。
- next():返回下一個元素泰偿。
- remove():移除一個元素(迭代器實現(xiàn),與 List 的 remove() 不同)蜈垮。
也就是說甜奄,集合框架中幾乎所有容器都實現(xiàn)了 Iterable,通過它們的 iterator() 方法獲取自己定義的實現(xiàn)了 Iterator 的迭代器窃款。
舉個栗子:
public class IterableTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
System.out.print(iterator.next());
}
}
}
比較簡單课兄,首先調用 list.iterator() 獲取 Iterator 實例,這里的 list 是一個 ArrayList晨继,所以獲取的就是 ArrayList 的迭代器實現(xiàn)烟阐。
那么看一下 ArrayList 的迭代器是怎樣的:
ArrayList # iterator()方法 和 內部迭代器 Itr
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
protected int limit = ArrayList.this.size;
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor < limit;
}
@SuppressWarnings("unchecked")
public E next() {
...
return (E) elementData[lastRet = i];
}
public void remove() {
...
try {
ArrayList.this.remove(lastRet);
...
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
...
}
}
可以看到 ArrayList 的 iterator() 創(chuàng)建自定義迭代器 Itr,Itr 是一個 Iterator 實現(xiàn)紊扬,實現(xiàn)了 hasNext()蜒茄、next() 等主要方法。
上面遍歷 ArrayList 的小栗子就是通過調用它的迭代器相關方法來實現(xiàn)的餐屎。
特別注意:
你可能注意到了檀葛,在描述 Iterator 的 remove() 方法時特意加了括號表面與 List 的 remove() 方法的不同。
那么在使用迭代器進行遍歷需要刪除元素時需要調用迭代器的 remove() 方法腹缩,調用 list 的 remove() 會造成并行修改異常(ConcurrentModificationException)屿聋。
錯誤示范:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String s = iterator.next();
if(s.equals("b")){
list.remove(s);
}
}
正確寫法應為:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String s = iterator.next();
if(s.equals("b")){
iterator.remove();
}
}
自己實現(xiàn)迭代器:
如果我們想自己寫一個集合并使用迭代器來遍歷的話,可以這樣寫:
public class MyList<E> implements Iterable<E> {
@Override
public Iterator<E> iterator() {
return new MyIterator();
}
private class MyIterator implements Iterator<E>{
@Override
public boolean hasNext() {
return false;
}
@Override
public E next() {
return null;
}
@Override
public void remove() {
}
}
}
2.2 接口 Collection
Collection 接口包含的方法藏鹊,AS 找到這個類润讥,快捷鍵 Ctrl + O:
可見 Collection 定義的這些方法有一些是我們經常用到的,比如 add()
添加元素盘寡,clear()
刪除所有元素楚殿。
正是因為 Collection 定義了這些接口,所有的實現(xiàn)類都受到該接口的管理竿痰。這里是接口的一個功能的體現(xiàn): 定義一個規(guī)范(協(xié)議)脆粥,這樣做的好處是 Collection 無需知道子類是如何實現(xiàn)的砌溺,而且擴展性高、解耦变隔。
接口的三大作用:實現(xiàn)多繼承抚吠、定義一個規(guī)范(協(xié)議),用于回調弟胀。
先看圖片的左邊部分:
AbstractCollection:實現(xiàn)了大部分的集合接口楷力。
2.2.1 接口 List extends Collection
特點:
- List 繼承了 Collection,它定義一個 允許重復的有序集合孵户。
- 因為是有序集合萧朝,所以查找元素效率高。
- 同時因為刪除和插入數據會引起其它元素位置改變夏哭,所以刪除和插入數據效率低检柬。
從實現(xiàn)方法來看,List 增加了允許在指定位置竖配,也就是下標操作元素的功能何址。同時增加了 ListIterator 迭代器,它是迭代器接口 Iterator 的子類进胯,主要用于定義雙向遍歷的功能用爪,具體由子類實現(xiàn)。
AbstractList extends AbstractCollection implements List:繼承于AbstractCollection 并且實現(xiàn)了大部分List接口胁镐,抽象類偎血。
AbstractSequentialList<E> extends AbstractList<E>:AbstractSequentialList 繼承 AbstractList,也是一個抽象類盯漂,用于提供了對元素鏈式訪問而不是隨機訪問颇玷。
ArrayList
特點:
- 數組實現(xiàn),大小可變就缆。
- 根據下標獲取元素帖渠,隨機訪問和遍歷元素時提供更好的性能。新增和移除元素時效率較低竭宰。
- 非同步空郊。
ArrayList 繼承了 AbstractList 且實現(xiàn) List,此外還實現(xiàn)了 Serializable羞延,說明它支持序列化渣淳,可用于傳輸和儲存相關。下面記錄 ArrayList 常用方法的實現(xiàn):
ArrayList # add():添加元素
//1.定義 Object 數組
transient Object[] elementData;
public boolean add(E e) {
//2.確定和記錄數組容量
ensureCapacityInternal(size + 1); // Increments modCount!!
//5.將新元素添加到新數組
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 3.確定容量
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//4.復制原有數據到新數組
elementData = Arrays.copyOf(elementData, newCapacity);
}
看注釋序號:
- 定義 Object 數組:定義這個數組用于儲存 ArrayList 中的數據伴箩,transient 修飾的變量不參與序列化,但是 ArrayList 實際使用時數據是可以進行序列化的鄙漏。原因可以參考下面:
也就是說 ArrayList 提供了自己的 writeObject()
方法所以可以進行序列化棺蛛,但是為什么不直接使用 Object[] elementData
呢?原因是 ArrayList 是動態(tài)數組可以自增長容量巩步,但是如果設定了數組容量是 100旁赊,而實際只放了一個元素,那么默認序列化后就會生成 99 個 null 元素椅野,這明顯是沒必要的终畅。所以 ArrayList 就提供了自己的序列化方法 writeObject()
在序列化時只遍歷實際元素,這樣就避免了指定長度生成無用元素的弊端竟闪。
以上答案參考:
- 確定和記錄數組容量:
ensureCapacityInternal()
和ensureCapacityInternal()
經過計算得出要創(chuàng)建的數組大小,這個容量用于創(chuàng)建新數組炼蛤。 - 新的容量為舊的容量加上它的一半妖爷,也就是說每次擴容會增加原來的 50%。
*這里的 ">>" 是移位預算符理朋,表示二進制的向右移動一位絮识,獲得的結果是原來的一半,反之則是原來的一倍嗽上。比如:4>>1 = 2次舌。那么 4<<1 = 8。 - 復制原有數據到新數組:利用
copyOf()
方法創(chuàng)建一個新數組兽愤,且容量已確定垃它,同時將原有數據復制給新的數組。 - 將新元素添加到新數組:
elementData[size++] = e;
實現(xiàn)了將新元素的添加并且下標加一烹看。
除了上面添加元素的方法国拇,其它常用的比如 indexOf()
其實是遍歷數組 elementData
中的所有元素并進行對比,如果存在相同元素則返回惯殊,反之返回 -1.
ArrayList # 其他主要方法
方法名 | 作用 | 重載/說明 |
---|---|---|
add(E e) | 添加元素 | add(int index, E element) 指定位置添加元素 |
addAll(Collection<? extends E> c) | 添加整個其它集合 | (int index, Collection<? extends E> c) 指定位置添加整個集合 |
clear() | 清除集合 | - |
contains(Object o) | 判斷是否存在該元素 | 遍歷實現(xiàn) |
remove(int index) | 移除元素 | remove(Object o) Object 參數的重載 |
subList(int fromIndex, int toIndex) | 返回從 fromIndex 到 toIndex 的列表 | 注意:返回的是原列表酱吝,進行修改原列表這段數據也會更改 |
ensureCapacity(int minCapacity) | 對底層數組進行擴容。 | 在確定要插入數據的數量時土思,首先調用此方法進行擴容务热,避免每次添加時拷貝數組,提高效率己儒。 |
ensureCapacityInternal(int minCapacity) | 同上 | 只供 ArrayList 內部使用的增加容量的方法崎岂,jdk1.7 加入。 |
trimToSize() | 去除 ArrayList 申請的多余儲存空間闪湾。 | ArrayList 每次自增原來的 50%冲甘,這就造成有一部分未使用的空間,該方法可以去除未使用的空間。 |
LinkedList
特點:
- 底層是鏈表實現(xiàn)江醇。
- 允許有 null(空)元素濒憋,主要用于創(chuàng)建鏈表數據結構。
- LinkedList 是一個雙鏈表陶夜,所以在添加和刪除數據時比 ArrayList 有更好的性能凛驮,但查找效率低。
- 非同步条辟∏玻可使用下列方法實現(xiàn)同步:
Listlist=Collections.synchronizedList(newLinkedList(...));
了解關于鏈表可參考我另一篇筆記:
屬性:LinkedList 有三個成員變量:
int size = 0; // LinkedList 長度
Node<E> first; // 首節(jié)點
Node<E> last; // 尾結點
方法:挑幾個重要的方法記錄
LinkedList # add(E e):添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
直接將新增的元素放到鏈表的最后面,并將 LinkedList 長度加 1羽嫡,修改的次數(modCount)加1本姥。
LinkedList # add(int index, E element):添加元素到指定位置
public void add(int index, E element) {
// 1. 檢測 index 是否合法
checkPositionIndex(index);
// 2. 判斷元素插入位置
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
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);
succ.prev = newNode;
// 3. 插入到當前元素之前
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
- 首先進行檢測,查看 index 是否大于等于 0 和 小于等于數組長度厂僧,如果不合法則拋出(這里代碼比較簡單扣草,省略掉了)。
- 進行判斷颜屠,如果插入到末尾辰妙,直接調用剛才也用到的
linkLast()
方法就好。反之調用linkBefore()
把要插入的元素放到下標元素之前甫窟。 - 如果下標元素的前一個元素為空密浑,則說明下標元素已經在隊首了,因為之前已經把下標元素的前元素指向新元素粗井,所以直接把新元素放到隊首即可尔破。最后進行 LinkedList 的長度(size)和修改次數(modCount)的增長。
LinkedList # get(int index):根據下標獲取元素浇衬,該方法是通過遍歷來獲取的
public E get(int index) {
// 1.判斷
checkElementIndex(index);
return node(index).item;
}
Node<E> node(int index) {
// 2. index 和鏈表長度的一半進行對比
if (index < (size >> 1)) { //2.1 小于一半
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {//2.2 大于一半
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
- 依舊是判斷下標合法性懒构。
- 下標和數組長度一半進行判斷。因為雙鏈表的特性耘擂,所以不需要進行全部元素的遍歷胆剧。
2.1 如果 index 小于鏈表長度的一半,只需遍歷鏈表的前半部分醉冤,遍歷到第 index-1 個元素的下一個元素就是需要的元素秩霍。
2.2 如果 index 大于鏈表的一半,遍歷后半部分蚁阳,拿前一個結點即可铃绒。
LinkedList # remove(int index):根據下標移除元素,移除的過程就是重新指向和值置空的過程:
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
// 1. 判斷是否是首元素
if (prev == null) {
first = next;
} else {// 1.1 不是首元素
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
// 2.當前元素的值置空
x.item = null;
size--;
modCount++;
return element;
}
首先依舊判斷要移除的元素 item 是否合法螺捐。
- 如果當前元素的前一個元素為 null颠悬,說明它是首元素矮燎。直接將首元素由下一個元素覆蓋,就完成了移除過程椿疗。
1.1 不是首元素則由下一個元素覆蓋當前元素漏峰,并把 x 的一個元素置空糠悼。因為 x 已經被替換了届榄,新來的有自己的 prev,所以也無需保存 x 的 prev 了倔喂。 - 把要移除的元素的值置空铝条,然后長度減 1,修改次數加 1席噩。
LinkedList # 其他主要方法
方法名 | 作用 | 重載/說明 |
---|---|---|
add(E e) | 添加元素 | add(int index, E element):相應下標添加元素班缰,屬于 List 的方法 |
offer(E e) | 添加元素 | 不違反容量限制的情況下,將元素插入到 Queue 中悼枢,屬于 Queue 的方法 |
addAll(Collection<? extends E> c) | 添加整個集合 | addAll(int index, Collection<? extends E> c) 指定位置添加整個集合 |
clear() | 清除所有元素 | - |
contains(Object o) | 判斷是否包含該元素 | 先遍歷前半段埠忘,后遍歷后半段 |
get(int index) | 根據下標獲取元素 | 也是分段遍歷忱详,找到就返回 |
set(int index, E element) | 更新某結點元素 | - |
remove(int index) | 根據下標移除元素 | - |
toArray() | 轉換為 Object 數組 | 遍歷鏈表結點數據廊遍,放到 Object[] 最后返回 |
Vector
特點:
- 同步:基本所有 public 方法或者其具體操作都添加同步關鍵字 synchronized搁吓。
- 效率較低:因為基本所有方法都是同步的饰潜,所以會造成性能損失粪狼。但其效率問題不止是因為這一個原因呈驶。
- 不推薦使用(...):可使用 Collections 的 synchronizedList 方法同步 ArrayList 來替代捂掰。
Stack extends Vector
繼承 Vector 實現(xiàn)的棧羡鸥。
主要方法有三:
- push(E item):添加元素蜈块。
- peek():返回棧頂元素鉴腻,不移除。
- pop():返回棧頂元素并移除百揭。
棧的實現(xiàn)方式也有很多爽哎,參考筆記里面有關棧的記錄:
2.2.2 接口 Set extends Collection
Set 集合三大特征:確定性、互斥性器一、無序性课锌。大部分實現(xiàn)此接口的集合都具有此特性。
特點:
- 強調元素無序性盹舞,但是不可重復产镐,重復的元素會被覆蓋掉。
- 檢索元素效率較低踢步,插入和刪除效率高且不會引起元素位置改變。
抽象類 AbstractSet
繼承了 AbstractCollection获印,實現(xiàn)了 Set 的大部分接口。這個抽象類就是為了方便擴展而存在的。
HashSet
特點:
- 底層實現(xiàn)是 HashMap唆缴。
- 不允許出現(xiàn)重復元素面徽,且不保證順序。因為它的數據存儲是用 HashMap 實現(xiàn)的趟紊。
- 允許包含值為 null 的元素碰酝,但最多只能一個。
HashSet # add(E e)
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
這里涉及到 HashMap 的特性:map 儲存時如果當前 map 沒有相同的 key铛嘱,則返回 null袭厂。
也就是說只有當新添加的值 e 為新的 key 時,才會返回 null嵌器,從而判定成功使 Set 加入這個值肛真。
所以,當 e 為 null 時也可以存進 map(HashMap key爽航、value 均可以為 null)蚓让,但是只能存一個 null,多了添加時會返回 false 存不進去讥珍。
HashSet # remove(Object o)
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
remove 的實現(xiàn)也比較簡單历极,調用內部 map 的 remove() 方法。返回的是被移除的 map 的值(猜的)衷佃,如果該值與儲存的 PRESENT 相同則表明從 Set 移除成功趟卸。
HashSet 遍歷
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
set.add("aaa");
set.add("bbb");
set.add("ccc");
set.add("ccc"); //故意加一個相同元素,遍歷出來確實沒它氏义。
//遍歷
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
//或者這樣
for (String s:set) {
System.out.println(s);
}
}
LinkedHashSet
特點:
- 底層是 LinkedHashMap 實現(xiàn)锄列。
- 繼承 HashSet,但是是鏈表實現(xiàn)惯悠,所以可以保證元素順序邻邮。
- 有序,允許為 null克婶。
- 非線程安全筒严,底層操作通過 LinkedHashMap 實現(xiàn)丹泉。
HashSet 構造函數
public LinkedHashSet() {
super(16, .75f, true);
}
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);
}
HashSet 調用了父類 HashSet 的構造函數:
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
- 第一個參數 initialCapacity 表示初始容量,可以看到默認的 LinkedHashSet 初始容易為 16鸭蛙。
- 第二個參數 loadFactor 表示裝載因子摹恨。比如這個值為 0.5,則每次容量達到一半時進行擴容(默認 16 達到 8 擴容為 32)娶视。那么當這個值為 0.75晒哄,當容量達到當前最大容量的 75% 時進行擴容揩晴。
為啥是 0.75 呢?哈哈寒锚,想笑刹前。
- 第三個參數作為標記與 HashSet 構造方法區(qū)分拣技。
TreeSet
特點:
- 基于 TreeMap 實現(xiàn)的有序集合。
- 最開始的圖上有寫 TreeMap 是繼承 SortedSet 的莫辨,但是我在 JDK1.8 的源碼中看到并沒有沮榜。
TreeSet 無參構造函數
public TreeSet() {
this(new TreeMap<E,Object>());
}
TreeSet 主要方法
方法名 | 作用 | 重載/說明 |
---|---|---|
add(E e) | 添加元素 | - |
addAll(Collection<? extends E> c) | 添加整個集合 | - |
first() | 返回集合中首個元素 | - |
last() | 返回集合中最后一個元素 | - |
pollFirst() | 刪除集合首個元素 | - |
pollLast() | 刪除集合最后一個個元素 | - |
lower(E e) | 返回小于該數值的最近元素 | 參數值可以不存在于 TreeSet 中 |
higher(E e) | 返回大于該數值的最近元素 | 參數值可以不存在于 TreeSet 中 |
floor(E e) | 返回小于或等于該數值的最近元素 | 同上 |
ceiling(E e) | 返回大于或等于該數值的最近元素 | 同上 |
comparator() | 如果TreeSet采用了定制排序,則該方法返回定制排序所使用Comparator振愿;如果TreeSet采用了自然排序萍歉,則返回null枪孩。 | - |
2.3 接口 Queue extends Collection
Queue 主要方法:
方法名 | 作用 | 說明 |
---|---|---|
boolean add(E var1) | 添加元素 | 添加失敗時會拋出異常 |
boolean offer(E var1) | 添加元素 | 添加失敗返回 false |
E remove() | 移除元素 | 失敗拋出異常 |
E poll() | 移除元素 | 失敗返回 false |
E element() | 獲取頭部元素,不移除 | 隊列為空拋出異常 |
E peek() | 獲取頭部元素攻询,不移除 | 隊列為空返回 null |
2.3.1 接口 Deque extends Queue
特點:
- 雙端隊列,支持從兩個端點檢索和插入元素拯杠。
方法名 | 作用 | 說明 |
---|---|---|
push(E) | 添加元素 | 失敗時拋出異常 |
addFirst(E) | 隊列頭部添加元素 | 失敗時拋出異常 |
addLast(E) | 隊列尾部添加元素 | 失敗時拋出異常 |
offerFirst(E) | 隊列頭部添加元素 | 失敗時返回 false |
offerLast(E) | 隊列尾部添加元素 | 失敗時返回 false |
getFirst() | 獲取隊列頭部元素 | 隊列為空時拋出異常 |
getLast() | 獲取隊列尾部元素 | 隊列為空時拋出異常 |
peekFirst() | 獲取隊列頭部元素 | 隊列為空時返回 null |
peekLast() | 獲取隊列尾部元素 | 隊列為空時返回 null |
removeFirstOccurrence(Object) | 刪除第一次出現(xiàn)的指定元素 | 不存在時返回false |
removeLastOccurrence(Object) | 刪除最后一次出現(xiàn)的指定元素 | 不存在時返回false |
pop() | 彈出隊列頭部元素 | 隊列為空時拋出異常 |
removeFirst() | 彈出隊列頭部元素 | 隊列為空時拋出異常 |
removeLast() | 彈出隊列尾部元素 | 隊列為空時拋出異常 |
pollFirst() | 彈出隊列頭部元素 | 隊列為空時返回 null |
pollLast() | 彈出隊列尾部元素 | 隊列為空時返回 null |
可以看出,Deque 在 Queue 的基礎上添加了針對隊首和隊尾的一些操作最蕾。
Deque 的具體實現(xiàn)
Deque 的直接實現(xiàn)有 ArrayDeque和LinkedList揖膜,它們共同特點是非阻塞拜隧,非線程安全洪添。
ArrayDeque
特點:
- 由 Object[] 來維護數據干奢,也就是數組實現(xiàn)忿峻。
- 在兩端增刪元素較為高效逛尚。
- 總體比 LinkedList 要更優(yōu)越到逊。
LinkedList
上面有聊到過 LinkedList 的特點觉壶,所以這里注重和 ArrayDeque 對比铜靶。
特點:
- 鏈表實現(xiàn)旷坦,實現(xiàn)了 Deque 需要有獲取頭和尾的功能,所以是一個雙端鏈表舌胶。
- 在兩端增刪元素效率較低,因為需要創(chuàng)建和刪除相關結點誊薄。
- 實現(xiàn)了 List 接口的所有操作切心,較為靈活绽昏。
Deque 的阻塞實現(xiàn)有 BlockingQueue 接口和五個阻塞隊列類全谤,阻塞隊列的特點是不是立即從隊列添加或刪除元素补憾,線程執(zhí)行操作阻塞直到有空間或元素可用余蟹。
阻塞隊列的五個實現(xiàn)類:
- ArrayBlockingQueue:一個由數組支持的有界隊列威酒。
- LinkedBlockingQueue:一個由鏈接節(jié)點支持的可選有界隊列葵孤。
- PriorityBlockingQueue:一個由優(yōu)先級堆支持的無界優(yōu)先級隊列尤仍。
- DelayQueue:一個由優(yōu)先級堆支持的宰啦、基于時間的調度隊列。
- SynchronousQueue:一個利用 BlockingQueue 接口的簡單聚集(rendezvous)機制漓柑。
那么它們是怎么實現(xiàn)阻塞的呢辆布,就選第一個實現(xiàn)類 ArrayBlockingQueue 的添加元素方法來看一下:
ArrayBlockingQueue # offer(E e)
public boolean offer(E e) {
Objects.requireNonNull(e);
// 1.獲取 ReentrantLock
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
// 2.進行插入數據的操作
enqueue(e);
return true;
}
} finally {
// 3.釋放鎖
lock.unlock();
}
}
根據注釋 1 2 3 來看,ArrayBlockingQueue 的添加元素的方法使用了 ReentrantLock 重入鎖惭蹂,滿足一定條件后執(zhí)行數據插入操作剿干,最后再釋放鎖。其它主要方法也都有用到重入鎖榜轿,所以說它是一個阻塞隊列谬盐。
其它實現(xiàn)了 BlockingQueue 幾個阻塞隊列基本都是通過加鎖的方式來實現(xiàn)阻塞的皇型,但其中的具體實現(xiàn)和鎖的功能和方式可能不同弃鸦,暫不深究吧唬格。
那么有關 List、Set 和 Queue 的內容先到這里门粪,下篇記錄 Map 相關和總結注服。