1. 集合 Collection
1.1 Java 集合框架
Java 集合框架位於 java.util 包中。Java 集合框架主要包括兩種類型的容器,一種是集合(Collection),存儲一個元素集合,另一種是圖(Map),存儲鍵/值對映射。Collection 接口又有 3 種子類型,List、Set 和 Queue,再下面是一些抽象類,最后是具體實現類,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。
集合框架是一個用來代表和操縱集合的統一架構。所有的集合框架都包含如下內容:
- 接口:Collection,List,Set,Map 等。用於以不同的方式操作集合對象
- 實現類:是集合接口的具體實現。如 ArrayList、LinkedList、HashSet、HashMap
- 算法:是實現集合接口的對象里的方法執行的一些有用的計算,如搜索和排序。這些算法被稱為多態,那是因為相同的方法可以在相似的接口上有着不同的實現
集合和數組的區別:
- 集合長度可變,數組長度固定
- 集合中只能是引用類型,數組中既可以是基本類型也可以是引用類型
- 集合中可以存儲不同類型的元素(通常只存放同種類型),數組只能存儲同一種類型
1.2 集合接口
接口 | 描述 |
---|---|
Collection | 最基本的接口,一個 Collection 代表一組對象,Java 不提供直接繼承自 Collection 的類,只提供其子接口。Collection 接口存儲一組不唯一,無序的對象。 |
List | List 接口是一個有序的 Collection,可以精確的控制每個元素的插入位置,通過索引訪問元素,允許有相同的元素。List 接口存儲一組不唯一,有序的對象。 |
Set | 具有與 Collection 完全一樣的接口,但不保存重復元素。Set 接口存儲一組唯一,無序的對象。 |
SortedSet | 繼承於 Set,保存有序的集合。 |
Map | 存儲一組鍵值對象,提供 key 到 value 的映射 |
SortedMap | 繼承與 Map,使 key 保持升序排列 |
1.3 Collection 集合的方法
方法 | 描述 |
---|---|
boolean add(E e) | 在集合末尾添加元素 |
boolean remove(E e) | 刪除值與 e 相等的元素,返回 true,若不存在,返回 false |
void clear() | 清除本類集中所有元素 |
boolean contains(E e) | 判斷集合中是否包含 e |
boolean isEmpty() | 判斷集合是否為空 |
int size() | 返回集合中的元素個數 |
boolean addAll(Collection c) | 將一個類集 c 中所有元素添加到另一個類集 |
Object[] toArray() | 返回一個包含本類集中所有元素的數組,數組類型為 Object[] |
Iterator iterator() | 迭代器,集合專用的遍歷方式 |
2. List
List 接口是一個有序的 Collection,可以精確的控制每個元素的插入位置,通過索引訪問元素,可以存儲相同元素。List 接口存儲一組不唯一,有序的對象。
List 共有三個實現類,ArrayList,Vector,LinkedList。
2.1 ArrayList
ArrayList 是最常用的 List 實現類,內部通過數組實現,允許堆元素進行快速隨機訪問。
由於數組存儲需要整塊內存空間,中間不能有間隔,當數組大小不足需要擴張時,需要將已有數組的數據全部復制到新存儲空間中。當從 ArrayList 中間插入或刪除元素時,需要對數組進行復制、移動,代價很高。因此只適合查找遍歷,不適用於頻繁的插入刪除。
特點:
- 底層數據結構為數組
- 查詢快,增刪慢
- 線程不安全,效率高
- 可以存儲重復元素
2.1.1 常用方法
方法 | 描述 |
---|---|
boolean remove(E e) | 刪除指定元素,返回是否刪除成功 |
E remove(int index) | 刪除指定索引處元素,返回被刪除元素 |
E set(int index, E e) | 修改指定索引處元素,返回被修改的元素 |
E get(int index) | 返回指定索引處的元素 |
int size() | 返回元素個數 |
boolean add(E e) | 將指定元素追加到集合末尾 |
void add(int index, E e) | 在集合中指定位置插入元素 |
2.2 LinkedList
LinkedList 是一個繼承於 AbstractSequentialList 的雙向鏈表,同時也可以被當做棧、隊列或雙端隊列操作。
LinkedList 實現了 List 接口,能對它進行隊列操作。
LinkedList 實現了 Deque 接口,即能將LinkedList當作雙端隊列使用。
LinkedList 實現了 Cloneable 接口,即覆蓋了函數clone(),能克隆。
LinkedList 實現 java.io.Serializable 接口,這意味着 LinkedList 支持序列化,能通過序列化去傳輸。
LinkedList 包含兩個重要成員 header 和 size。 header 是雙向鏈表的表頭,是雙向鏈表節點對應的類 Entry 的實例,Entry 中包含成員變量:previous,next,element,element 是該節點中存放的數據。
特點:
-
底層數據結構是雙向鏈表
-
查詢慢,增刪快
-
線程不安全,效率高
-
可以存儲重復元素
2.2.1 常用方法
方法 | 描述 |
---|---|
boolean add(E e) | 在鏈表末尾添加新節點 |
void add(int index, E e) | 在鏈表指定位置添加新節點 |
void addFirst/push(E e) | 在鏈表表頭添加新節點 |
void addLast/offer(E e) | 在鏈表表尾添加新節點 |
E removeFirst/pop() | 刪除第一個節點,並返回該對象 |
E removeLast() | 刪除最后一個節點,並返回該對象 |
E remove(int index) | 刪除指定位置節點 |
E get(int index) | 得到指定位置節點 |
int indexOf(E e) | 返回對象在鏈表中首次出現的位置,如沒有返回 -1 |
int lastIndexOf(E e) | 返回對象在鏈表中最后一次出現的位置,如沒有返回 -1 |
2.3 Vector
與 ArrayList 基本相同,通過數組實現,最大的區別是支持線程同步,某一時刻只有一個線程能夠修改 Vector。但線程同步需要很高的消費,訪問比 ArrayList 慢。
特點:
- 底層數據結構是數組
- 查詢快,增刪慢
- 線程安全,效率低
- 可以存儲重復元素
- 使用 synchronized 方法保證線程安全
3. Set
Set 具有與 Collection 完全一樣的接口,Set 中的對象不可重復,除此之外可以把 Set 當做 Collection 使用。該集合用於存儲無序元素,對象是否重復根據 hashCode 判斷。
如果想讓兩個不同的對象被視為相等,就必須覆蓋 Object 的 hashCode 方法和 equals 方法。
3.1 HashSet
HashSet 底層數據結構采用哈希表實現。元素的唯一性通過該元素類型是否重寫 hashCode() 和 equals() 方法來保證。
HashSet 首先判斷兩個元素的哈希值,如果哈希值相同,會接着通過 equals 方法比較,如果 equals 也為 true,則視為同一個元素。
HashSet 通過 hashCode 的值來確定元素在內存中的位置,一個 hashCode 位置上可以存放多個元素(多個元素存放在一個哈希桶中)。
特點:
- 底層數據結構為哈希表
- 元素無序且唯一
- 線程不安全
- 效率高
- 可存儲 null 元素
3.2 LinkedHashSet
LinkedHashSet 繼承於 HashSet,底層使用 LinkedHashMap 保存所有元素。方法操作上與 HashSet 相同。
特點:
- 底層使用 LinkedHashMap 保存元素
- 元素順序與存儲順序一致
- 元素唯一
- 線程不安全,效率高
3.3 TreeSet
TreeSet 的底層數據結構為紅黑樹。實現了 SortedSet 接口,可以隨時確保集合中的元素處於有序狀態。
TreeSet 每插入一個對象都會進行排序,將其插入紅黑樹的指定位置,Integer 和 String 對象都可以進行默認的 TreeSet 排序,但自定義對象不可以。自定義類必須實現 Comparable 接口,並重寫 compareTo() 方法才能使用 TreeSet。
重寫 compareTo() 方法時,比較不同的值,需要根據小於、等於、大於來返回相應的負整數,零或正整數。
特點:
- 底層數據結構為紅黑樹
- 元素有序且唯一
- 只能添加同種類型對象
- 不支持快速隨機訪問
- 不允許元素為 null
3.3.1 常用方法
方法 | 描述 |
---|---|
E higher(E e) | 返回集合中大於等於給定元素的最小元素 |
E lower(E e) | 返回嚴格小於給定元素的最大元素 |
E higher(E e) | 返回嚴格大於給定元素的最小元素 |
SortedSet headSet(E e) | 返回此 Set 中所有小於 e 的子集 |
SortedSet tailSet(E e) | 返回此 Set 中所有大於 e 的子集 |
4. Map
List 和 Set 都繼承自 Collection 接口,Map 不是。Map 用於保存具有映射關系的數據 key 和 value,通過鍵 key 查找值 value,它們可以是任何引用類型的數據,但 key 不能重復。
4.1 Map 的主要方法
方法 | 描述 |
---|---|
void clear() | 刪除全部映射 |
boolean containsKey(E key) | 查詢 Map 中是否包含指定 key |
boolean containsValue(E value) | 查詢 Map 中是否包含指定 value |
boolean isEmpty() | 查詢 Map 是否為空 |
E get(E key) | 返回指定 key 對應的 value |
E put(E key, E value) | 添加一對映射,如果有相同的 key 覆蓋 value |
void putAll(Map m) | 將指定 Map 中映射復制到 Map 中 |
E remove(E key) | 刪除指定 key 對應的映射,返回關聯的 value |
int size() | 返回 Map 中的映射對數 |
Set entrySet() | 返回 Map 中所包含映射組成的 Set 集合,每個集合元素都是 Map.Entry 對象 |
Set keySet() | 返回 Map 中所有 key 組成的 Set 集合 |
Collection values() | 返回 Map 中所有 value 組成的 Colllection |
4.2 HashMap
HashMap 的主干是一個 Entry 數組。Entry 是 HashMap 的基本組成單元,每一個 Entry 包含一個 key-value 鍵值對。為了解決哈希沖突 HashMap 采用了鏈地址法,也就是數組+鏈表的方式。
因此大多數情況下 HashMap 可以直接定位到值,訪問速度極快,遍歷順序不確定。HashMap 只允許一個鍵為 null,允許多條值為 null。且 HashMap 非線程安全。
JDK 8 對 HashMap 進行了修改,最大的不同是引入了紅黑樹提升 HashMap 的查找效率。在 JDK 8 中,為解決哈希沖突的鏈表中的元素超過 8 個以后,會將鏈表轉換為紅黑樹,在此處查找是可以把時間復雜度降低至 O(logN)。
HashMap的構造函數
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
initialCapacity 為 HashMap 的初始大小,loadFactor 為負載因子。
如果不填入構造參數,默認初始大小為16,負載因子為0.75。
特點:
- 底層數據結構:數組代表一對映射,鏈表/紅黑樹解決哈希沖突
- 訪問速度快,效率極高
- 遍歷順序不確定
- 非線程安全
4.3 LinkedHashMap
LinkedHashMap 是 HashMap 的一個子類,底層數據結構是用雙向鏈表連接起來的 HashMap,可以保留插入順序,其存取過程基本與 HashMap 相同。
LinkedHashMap 通過維護一個額外的雙向鏈表保證了迭代順序。該迭代順序可以是插入順序,也可以是訪問順序。因此,根據鏈表中元素的順序可以將其分為:保持插入順序的 LinkedHashMap 和 保持訪問順序的 LinkedHashMap,其中 LinkedHashMap 的默認按插入順序排序。
因此 LinkedHashMap 可以很好的支持 LRU 算法。
特點:
- 底層數據結構:雙向鏈表連接 HashMap 中的 Entry 數組
- 可按插入順序或訪問順序存儲
- 可以很好的支持 LRU 算法
- 非線程安全
4.4 ConcurrentHashMap
ConcurrentHashMap 支持並發操作,是安全高效的 Map 集合,廣泛應用於多線程開發。並發控制使⽤ synchronized 和 CAS 來操作,synchronized 只鎖定當前鏈表或紅⿊⼆叉樹的首節點,這樣只要 hash 不沖突,就不會產⽣並發,效率極大的提升。
JDK 8 后 ConcurrentHashMap 的數據結構改為 Node 數組+鏈表/紅黑樹的結構。
紅黑樹結構略有不同,HashMap 的紅黑樹中的節點叫做 TreeNode,TreeNode 不僅僅有屬性,還維護着紅黑樹的結構,比如說查找,新增等等。ConcurrentHashMap 中紅黑樹被拆分成兩塊,TreeNode 僅僅維護的屬性和查找功能,新增了 TreeBin,來維護紅黑樹結構,並負責根節點的加鎖和解鎖。
-
Node:Node 對象是鏈表上的一個節點,內部存儲 key、value,以及下一個節點的引用。節點中的 value 和 next 都用 volatile 修飾,保證並發的可見性
-
ForwardingNode:當擴容時,要把鏈表遷移到新的哈希表,在做這個操作時,會在把數組中的頭節點替換為 ForwardingNode 對象。ForwardingNode 中不保存 key 和 value,只保存擴容后哈希表(nextTable)的引用。此時查找相應 node 時,需要去 nextTable 中查找
-
TreeBin:當鏈表轉為紅黑樹后,數組中保存的引用為 TreeBin,TreeBin 內部不保存 key 或 value,它保存了 TreeNode 的 list 以及紅黑樹 root
ConcurrentHashMap 中添加映射的過程(put 方法)
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key, value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
具體流程:
- 給輸入的 key 分配 hashcode,分配好后依次判斷
- 判斷是否需要初始化
- 判斷 key 對應的 Node 數組是否為空,如果為空則用 CAS 寫入數據
- 判斷是否需要擴容
- 如果都不滿足,則利用 synchronized 鎖寫入數據
- 如果鏈表長度大於
TREEIFY_THRESHOLD
,則將鏈表改為紅黑樹
特點:
- 底層數據結構:Node數組+鏈表/紅黑樹
- 線程安全高效,多線程使用
- 並發控制使用 synchronized 和 CAS,synchronized 只鎖定當前鏈表或紅⿊⼆叉樹的首節點
4.5 HashTable
線程安全的 HashMap,同一時間只有一個線程能夠修改 HashTable,並發性遠遠不如 ConcurrentHashMap,效率低下,不建議在代碼中使用。
4.6 TreeMap
TreeMap 實現了 SortedMap 接口。能夠把保存的記錄根據鍵排序,默認為鍵值升序,也可以指定排序的比較器。如果使用排序的映射,建議使用 TreeMap。
TreeMap 底層為紅黑樹,將代表一對對映射的 Entry 數組作為節點連接成紅黑樹,不需要調優,紅黑樹自動平衡。
在使用 TreeMap 來保存自定義對象時,與 TreeSet 相同必須讓自定義對象的類實現 Comparable 接口,並重寫 compareTo() 方法。
特點:
- 底層數據結構為紅黑樹
- 存入映射按鍵值排序,默認升序
- 非線程安全
4.6.1 常用方法
方法 | 描述 |
---|---|
Map.Entry firstEntry() | 返回最小 key 對應鍵值對 |
Map.Entry lastEntry() | 返回最大 key 所對應的鍵值對 |
E firstKey() | 返回最小 key |
E lastKey() | 返回最大 key |
Map.Entry higherEntry(E key) | 返回位於 key 后一位的鍵值對 |
Map.Entry lowerEntry(E key) | 返回位於 key 前一位的鍵值對 |
E lowerKey(E key) | 返回位於 key 前一位的 key |
E higherKey(E key) | 返回位於 key 后一位的 key |