Java集合——List,Set,Map總結筆記


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM