ArrayList、Vector、HashMap、HashTable、HashSet的默認初始容量、加載因子、擴容增量、具體區別
要討論這些常用的默認初始容量和擴容的原因是:
當底層實現涉及到擴容時,容器或重新分配一段更大的連續內存(如果是離散分配則不需要重新分配,離散分配都是插入新元素時動態分配內存),要將容器原來的數據全部復制到新的內存上,這無疑使效率大大降低。
加載因子的系數小於等於1,意指 即當 元素個數 超過 容量長度*加載因子的系數 時,進行擴容。
另外,擴容也是有默認的倍數的,不同的容器擴容情況不同。
List 元素是有序的、可重復的:
ArrayList、Vector默認初始容量為10
Vector:線程安全,但速度慢
底層數據結構是數組結構
加載因子為1:即當 元素個數 超過 容量長度 時,進行擴容
擴容增量:原容量的 1倍
如 Vector的容量為10,一次擴容後是容量為20
ArrayList:線程不安全,查詢速度快
底層數據結構是數組結構
擴容增量:原容量的 0.5倍+1
如 ArrayList的容量為10,一次擴容後是容量為16
Set(集) 元素無序的、不可重復。
HashSet:線程不安全,存取速度快
底層實現是一個HashMap(保存數據),實現Set接口
默認初始容量為16(為何是16,見下方對HashMap的描述)
加載因子為0.75:即當 元素個數 超過 容量長度的0.75倍 時,進行擴容
擴容增量:原容量的 1 倍
如 HashSet的容量為16,一次擴容後是容量為32
Map是一個雙列集合
HashMap:默認初始容量為16,長度始終保持2的n次方
(為何是16:16是2^4,可以提高查詢效率,另外,32=16<<1 -->至於詳細的原因可另行分析,或分析源代碼)
加載因子為0.75:即當 元素個數 超過 容量長度的0.75倍 時,進行擴容
擴容增量:原容量的 1 倍
如 HashMap的容量為16,一次擴容後是容量為32
HashTable:默認初始容量為11
線程安全,但是速度慢,不允許key/value為null
加載因子為0.75:即當 元素個數 超過 容量長度的0.75倍 時,進行擴容
擴容增量:2*原數組長度+1
如 HashTable的容量為11,一次擴容後是容量為23
HashTable和HashMap的具體區別:
Hashtable 和 HashMap 做為 Map 的基本特性
兩者都實現了Map接口,基本特性相同
- 對同一個Key,只會有一個對應的value值存在
- 如何算是同一個Key? 首先,兩個key對象的hash值相同,其次,key對象的equals方法返回真
內部數據結構
Hashtable和HashMap的內部數據結構相似
其基本內部數據結構是一個Entry數組 (transient Entry[] table)
- 數組元素為實現Map.Entry<K,V>接口的類,Hashtable和HashMap各自實現了自己的Entry類。
- Entry包含一個Key-value對,以及一個next指針指向另一個Entry。多個Entry可以組成一個單向鏈表。
常用操作
數據插入操作: put(key,value)
- 根據Key的hash值計算出該Entry所應存放的位置(數組下標)
- 若該數組元素為空,直接放置Entry到此處
- 若多個不同的Key所計算得到的數組下標相同,新加入的Key-value對(Entry)會被加入到Entry單向鏈表中。Hashtable和HashMap都是將其插入鏈表首部.
- 若已經有相同的Key存在於這個鏈表中,則,新的value值會取代老的value
- 當Map中存放的Entry數量超過其限制( 數組長度 * 負荷因子)時,Map將自動重新調整數組大小並重新對Entry進行散列
數據查找:get(key)
- 根據Key的hash值計算出該Entry對所應存放的位置(數組下標)
- 得到該位置的第一個Entry對象,比較key和Entry.key,若hash值相同,並且equals為真,則該Entry是我們要找的Key-value對,否則繼續沿next指針構成的單向鏈表查找
數據移除:remove(key)
- 按照上述數據查找的方式找到key所在的Entry對象,將其移除,並保持Entry單向鏈表的連通性
Hashtable 和 HashMap 的比較
一般情況下,HashMap能夠比Hashtable工作的更好、更快,主要得益於它的散列算法,以及沒有同步。應用程序一般在更高的層面上實 現了保護機制,而不是依賴於這些底層數據結構的同步,因此,HashMap能夠在大多應用中滿足需要。推薦使用HashMap,如果需要同步,可以使用同 步工具類將其轉換成支持同步的HashMap。
Map的效率
Map的效率與Entry數組大小及負荷因子的選取有密切關系。選取適當的數組大小有利於Key-value對的散列分布,並且,如果數組足夠 大,將有效的減少重新調整數組的次數,提高效率。較小的負荷因子將占用更多的空間,但降低沖突的可能性,從而將加快訪問和更新的速度。
另外,Key的hash值本身如果能保證較好的散列性,也有益於提高Map的讀寫效率。在effective java中,對hash()的重載有好的建議。
辨析
“Hashtable和HashMap的區別主要是前者是同步的,後者是快速失敗機制保證不會出現多線程並發錯誤(Fast-Fail)。”,這是一個被很多文章轉載過的概念,但其描述並不準確,容易引起誤會。
實質上,Fast-fail與同步保護的是兩種不同情況下的並發,兩者不能拿來做比較。
Hashtable是同步的,在執行get,put,remove,size,clear等一次性讀寫操作時,使用了同步機制,避免了多個線程 同時讀寫Hashtable。但同步機制並不能避免在iterator或Enumeration遍歷過程中其他線程對Hashtable的put、 remove、clear操作,這些寫操作都會被毫無阻攔得成功執行。
快速失敗機制主要目的在於使iterator遍歷數組的線程能及時發現其他線程對Map的修改(如put、remove、clear等),因 此,fast-fail並不能保證所有情況下的多線程並發錯誤,只能保護iterator遍歷過程中的iterator.next()與寫並發.
其次,Hashtable的iterator遍歷方式也是支持fast-fail的,不能說它沒有快速失敗機制。寫一個簡單的例程就可以證明這 一點,一個線程做iterator遍歷,另一個線程向hashtable中put新的key和value,很容易就會觀察到fast-fail 機制報告ConcurrentModificationException
引用:小明快點跑;小明快點跑
ArrayList、Vector、HashMap、HashTable、HashSet的默認初始容量、加載因子、擴容增量、具體區別