1. 程式人生 > >ArrayList、Vector、HashMap、HashTable、HashSet的默認初始容量、加載因子、擴容增量、具體區別

ArrayList、Vector、HashMap、HashTable、HashSet的默認初始容量、加載因子、擴容增量、具體區別

以及 內存 高效率 數組元素 調整 增量 [] key存在 集合

要討論這些常用的默認初始容量和擴容的原因是:

當底層實現涉及到擴容時,容器或重新分配一段更大的連續內存(如果是離散分配則不需要重新分配,離散分配都是插入新元素時動態分配內存),要將容器原來的數據全部復制到新的內存上,這無疑使效率大大降低。

加載因子的系數小於等於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的默認初始容量、加載因子、擴容增量、具體區別