本文源自參考《Think in Java》,多篇博文以及閱讀源碼的總結java
Java的集合其實就是各類基本的數據結構(棧,隊列,hash表等),基於業務需求進而演變出的Java特有的數據結構(由於不只僅是基本數據結構)。如今,咱們以數據結構的視角來看看Java的集合究竟是什麼樣子。並分析他們的性能。數組
JAVA的集合體系分爲兩類,Collection接口和Map接口數據結構
主要分爲三種:併發
其中Set和List繼承自Collection接口,Map則就是Map接口。性能
接口中都定義了一些基本增刪改查的方法。優化
具體繼承體系以下圖:3d
基本能夠從名字知道集合的內部數據結構。blog
下面大略說下每一個集合的數據結構,懶得貼源碼了。排序
最經常使用的List就是ArrayList和LinkedList了,在此不討論併發的List集合。
討論下底層源碼對它們的具體實現。繼承
使用JAVA的基本數組實現的動態數組集合,源碼底層維護着List的容量與實際長度。
由於使用的基本數組,不像哈希數組同樣須要考慮哈希碰撞問題,所以負載因子默認爲1。當List數組容量不夠時才進行擴容,擴容的倍數爲1.5倍。
經過Arrays.copyOf方法,返回複製的新數組。Arrays.copyOf底層調用的System.arraycopy方法。而在ArrayList初始化時,若是不指定初始數組長度,在JDK1.6以後默認初始長度爲0,在JDK1.6以前則默認爲10。在JDK1.6後,ArrayList在第一次擴容時,若是擴容長度不足10,則會直接擴容到10。
具體集合怎麼使用就不廢話了。
這是一個雙向鏈表,其中節點用的是LinkedList的內部類。和數據結構中的鏈表差很少。能夠用它實現棧和隊列。
很明顯,ArrayList是某種程度上的哈希表,適合隨機讀,可是不適合在集合中間插入和刪除(會形成後續數據的位移)。
而LinkedList適合在頭尾部插入刪除,不適合隨機讀。
值得一提的是ArrayList隨機讀的時間複雜度是O(1),LinkedList是O(n)。而ArrayList在中間插入和刪除的時間複雜度是O(n),LinekdList在中間插入刪除時間複雜度也是O(n)
能夠明顯看出來ArrayList在插入刪除上和LinkedList理論上所用的時間是一個級別的,可是ArrayList慢於LinkedList是由於在修改集合後須要進行其餘數組數據的移動,而LinkedList則是查找節點花費了O(n),不須要額外移動數據,因此在一樣數據量時,LinkedList進行數據修改優於ArrayList。
最經常使用的Map就是HashMap和TreeMap。
HashMap是底層用哈希數組實現的Map。HashMap就是一個個Entry(Key-Value鍵值對)存儲在一個哈希數組上(Entry是HashMap的內部類)。
哈希數組的使用不可避免的須要考慮哈希碰撞問題,經常使用的解決方案有:
在JDK裏,使用的就是拉鍊法解決的哈希碰撞問題,所以每一個哈希數組上的數組元素(又被稱爲桶——bucket),都是一個鏈表的表頭。這樣基本保證了HashMap的平均查找時間是O(1)。
HashMap的負載因子爲0.75
可是當出現頻繁哈希碰撞時,會致使某個鏈表過長進而致使了查找時間會趨近於O(n)。對此JDK本來的解決方案是設置負載因子爲0.75。當哈希表總負載量達到0.75時,就會進行擴容,擴容爲本來的2倍。這樣當數據平均下來後,不太容易出現過長的鏈表(由於擴容會分解鏈表從新放入桶中)。
可是這並無解決特殊狀況下查找效率的問題,只是讓這種特殊狀況更難以出現了。
JDK1.8中 HashMap出現了紅黑樹
所以在JDK1.8中又作出了改進,當某個桶中的鏈表的長度大於8時。鏈表會重構成一個紅黑樹。這樣保證了HashMap的最壞時間複雜度也僅僅是O(logn)。同時負載因子引發的擴容也保證了紅黑樹的重構不會頻繁發生,不會由於頻繁建樹致使過多的性能開銷。
HashMap的初始化與擴容
另外值得一說的就是HashMap在不知道初始長度進行初始化時,JDK1.6前默認長度爲16,JDK1.6後默認長度爲0。基本在JDK1.6中,須要初始化底層容器的集合都作出了這種優化。不會提早構造底層容器形成開銷,會等到使用時才進行底層的初始化。
而HashMap默認長度設置爲16,而且每次擴容都是2倍。這是爲了方便底層的哈希數組進行取模時的運算,能夠把取模的除法運算改寫成位移運算,提高性能。
而且在JDK1.8中,HashMap關於取模運算還作了另外一個優化。在JDK1.8以前,每次哈希數組擴容時,鏈表裏的數據都會再次進行哈希運算。而在JDK1.8後,不須要再進行運算了,只須要在每一個桶中選擇一半數據日後移動oldLength位就行(oldLength是集合在擴容前的容量)。
而另外一個經常使用的Map——TreeMap,底層就是用JAVA寫了一個紅黑樹,感受沒什麼好說的。有興趣的能夠回去翻翻數據結構的書。
HashMap的每一個Node還會以插入順序相互關聯成爲雙向鏈表。
Set主要是SortedSet和HashSet。打開源碼一看,分別new了一個TreeMap和HashMap,而後把數據存在了Key裏。嗯,這就是Set的底層實現了。