一、概述
ArrayList:數組集合。 查詢、修改、新增(尾部新增)快,刪除、新增(隊列中間)慢,適用於查詢、修改較多的場景。
LinkedList:雙向鏈表集合。查詢、修改慢(需要遍歷集合),新增,刪除快(只需要修改前后節點的鏈接即可),適用於新增、刪除較多的場景。
HashMap:結合數組和鏈表的優勢,期望做到增刪改查都快速,時間復雜度接近於O(1)。當hash算法較好時,hash沖突較低。適用於增刪改查所有場景。
二、分敘
ArrayList
-
ArrayList底層實現是基於數組的,因此對指定下標的查找和修改比較快,但是刪除和插入操作比較慢。
-
構造ArrayList時盡量指定容量,減少擴容時帶來的數組復制操作,如果不知道大小可以賦值為默認容量10。
-
每次添加元素之前會檢查是否需要擴容,每次擴容都是增加原有容量的一半。(擴容是創建一個新的數組,並將原來的數組元素遷移到新數組中)
-
每次對下標的操作都會進行安全性檢查,如果出現數組越界就立即拋出異常。
-
ArrayList的所有方法都沒有進行同步,因此它不是線程安全的。
-
以上分析基於JDK1.7,其他版本會有些出入,因此不能一概而論
LinkedList
數據結構
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; } }
-
LinkedList是基於雙向鏈表實現的,不論是增刪改查方法還是隊列和棧的實現,都可通過操作結點實現
-
LinkedList無需提前指定容量,因為基於鏈表操作,集合的容量隨着元素的加入自動增加(無需執行默認長度,也沒有擴容需求)
-
LinkedList刪除元素后集合占用的內存自動縮小,無需像ArrayList一樣調用trimToSize()方法
-
LinkedList的所有方法沒有進行同步,因此它也不是線程安全的,應該避免在多線程環境下使用
-
LinkedList根據index查詢時采取的是二分法,即index小於總長度一半時從鏈表頭開始往后查找,大於總長度一半時從鏈表尾往前查找。如果是根據元素查找,則需要從頭開始遍歷
-
以上分析基於JDK1.7,其他版本會有些出入,因此不能一概而論。
HashMap
數據結構
static class Entry<K,V> implements Map.Entry<K,V> { final K key; //鍵 V value; //值 Entry<K,V> next; //下一個Entry的引用 int hash; //哈希碼 ... //省略下面代碼 }
哈希圖

- 哈希表是由數組和單項鏈表共同構成的一種結構,上圖中一個數組元素鏈表存在多個元素,說明存在hash沖突,理想情況下每個數組元素只應包含一個元素
- 擴容原因:HashMap默認的初始容量為16,默認的加載因子是0.75。而threshold是集合能夠存儲的鍵值對的閥值,默認是初始容量*加載因子,也就是16*0.75=12,當鍵值對要超過閥值時,意味着這時候的哈希表已處於飽和狀態,再繼續添加元素就會增加哈希沖突,從而使HashMap的性能下降。
- 每次擴容都是增加原有容量的一倍。(擴容是創建一個新的數組,並將原來的數組元素遷移到新數組中,根據hash值重新分配)
- hash值的計算方式(字符串是單獨的計算方式,擾動函數就是把所有東西雜糅到一起,提高隨機性)
//生成hash碼的函數 final int hash(Object k) { int h = hashSeed; //key是String類型的就使用另外的哈希算法 if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); //擾動函數 h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
三、總結
概述中已經描述各個集合的適用場景,這里重點說一下HashMap。HashMap可以通過hash值快速定位到數組下標,執行新增、修改、刪除操作。當hash算法較好(hash沖突較少)時,增刪改查的時間復雜度都是O(1)。但是如果鏈表較長,則會增加增刪改查的時間復雜度O(鏈表長度)。原則就是盡量減少hash沖突,並預先估算hashmap長度,減少擴容操作。