Java集合是咱們使用最頻繁的工具,也是面試的熱點,但咱們對它的理解僅限於使用上,並且大多數狀況沒有考慮過其使用規範。本系列文章將跟隨源碼的思路,分析實現的每一個細節,以期在使用時避免各類不規範的坑。在這裏,咱們會驚豔於開發者優秀的設計,也會感激先輩們付出的艱辛努力,更重要的是知其因此然,少犯錯誤,寫出優秀的代碼。git
許多人對集合類的理解是暴力的,當須要保存對象時就使用ArrayList
,當須要保存鍵值對時就使用HashMap
,當須要不可重複時就使用HashSet
,等等。並且使用方式也比較單一:github
List<String> list = new ArrayList<>();
Map<String, String> map = new HashMap<>();
Set<String> set = new HashSet<>();
// ...
複製代碼
這裏咱們先不考慮多線程安全問題,這個問題一般有專門的類實現,或者能夠經過Collections.synchronizedXXX
方法解決。除此以外,咱們真的能夠如此簡單的使用集合嗎?面試
假如數據只有幾百、幾千個,那麼使用何種方式實現差異並不大。但當咱們須要處理大數量級的數據時,採用不一樣的方式效率可能相差百倍甚至更多,這種狀況下性能將變得格外重要。例如分別存儲於ArrayList
和LinkedList
的100萬條數據,要獲取位於位置 i 的元素,前者能夠瞬間完成,後者則可能須要數秒。這時,使用哪一個集合類,怎樣合理使用就是咱們必須掌握的技能了。數據庫
若是你也像以上這般使用集合,或者不知道如何優化集合的使用,你都應該讀本系列文章。若是你僅有一些點不清晰,也能夠在這裏找到答案。或者你只是不想閱讀枯燥的源碼,卻對原理很好奇,你也能夠閱讀本系列文章。若是你只是想應付面試,我想當你堅持把這些文章讀完後,你會以爲面試好像也不那麼重要了。編程
本系列文章立足於深入理解Java集合的原理與實現,讀完這些文章後你將得到如下知識:數組
大量的數據結構知識。安全
ArrayList有那麼多構造函數,使用不一樣的構造函數會有區別嗎?bash
ArrayList是如何擴容的?微信
LinkedList如何提供經過位置獲取數據的功能的,它的查詢效率真的很是低嗎?數據結構
用數組能夠實現隊列嗎?
影響HashMap性能的因素有哪些?
複雜的紅黑樹是如何實現的?
LRUCache的底層原理是什麼?
……
對數據的操做,大抵就是增、刪、改、查,以及在某些時候根據位置獲取數據,有時可能還須要進行排序。改和查又能夠理解爲一致的操做,由於要修改一條數據須要先找到它,而後替換便可。接下來咱們就從增、刪、查這三點簡要分析下當前使用比較普遍的幾種數據結構。
數組在內存中佔據一段連續的內存,全部的數據在內存中連續排列。它的大小是固定的,這一特性使得數組對於插入操做並不友好,咱們分析ArrayList
時就會看到這種操做的複雜。但數組對於位置的訪問是極其友好的,它支持所謂RandomAccess
特性,這使得基於位置的操做能夠迅速完成,其時間複雜度爲O(1)。數組的數據順序與插入順序一致,因此查詢操做須要遍歷,其時間複雜度爲O(n)。
因此數組最大的優點在於基於位置的訪問,在擴展性方面表現無力。
不一樣於數組,鏈表是經過指針域來表示數據與數據之間的位置關係的,因此鏈表在頭部或尾部插入數據的複雜度僅爲O(1)。鏈表不具有RandomAccess
特性,因此沒法提供基於位置的訪問。其查詢操做也必須從從到尾遍歷,複雜度爲O(n)。
因此鏈表最大的優點在於插入,而查詢的表現很通常。
那有沒有一種結構可以結合數組和鏈表的優勢,使得查詢和插入都具備優秀的表現呢?答案是確定的,這就是散列表。
散列表就是Hash Table,這種結構使用key-value
形式存儲數據,咱們常用的HashMap
、HashTable
就基於它。
數組和鏈表在查詢時表現通常的緣由在於它們並不記得數據的位置,因此只能用待查詢的數據和存儲的數據依次比對。散列表使用一種巧妙的方式來減小甚至避免這種依次比對,它的原理是經過一個函數把任何的key轉爲int,每次查找時只須要執行一次這個函數即可以迅速定位。這個過程是否是像查字典呢?
散列表並不像上述那般完美,由於並不會有一個函數,可以保證全部的key轉換結果都不一樣,也就是會發生所謂的哈希碰撞
,並且它必須依賴於其餘的數據結構,這部分知識會在後續文章中詳細介紹。
良好設計的散列表可使增、刪、查等操做的時間複雜度均爲O(1)。
二叉排序樹是解決查詢問題的另外一方案,若是數據在插入時是有序的,在查詢時就可使用二分法。二分法的原理很簡單,好比猜一個在0-100之間的數,第一次猜50就能夠直接排除一半的數據,每次按照這個規則就能夠很快的獲取正確答案。二分法的時間複雜度爲O(lg n)。
樹的結構對二分法有自然的支持(但這不是樹最重要的用途)。二叉排序樹犧牲了一部分插入的時間,但提升了查詢的速度,同時有序的數據也能夠作些其餘的操做。若是查詢的操做重要性超過了插入,咱們應該考慮這種結構。二叉排序樹也存在一些不平衡致使效率降低的問題,因此有了AVL樹、紅黑樹,以及用於數據庫索引的B樹、B+樹等概念,關於二叉排序樹的知識也會在後續文章中介紹。
以上介紹的數據結構的知識是咱們理解Java集合類的基礎,掌握這些核心原理,咱們分析集合類源碼時纔不會吃力,咱們會先對這些數據結構進行簡要介紹,其餘和本系列文章無關的概念不會涉及,你們能夠查閱相關專業書籍進行系統學習。
因爲集合類的源碼十分龐大,從接口抽象設計到具體實現涉及到數十個類,咱們不可能每行代碼都進行分析,一些在前面分析過的點在後續部分也會略過,但對於咱們應該注意的點都會詳細解讀。有一些過於複雜的代碼,還會用圖示進行直觀的演示,以幫助理解整個運行機制。
文章中會不可避免地粘貼大量源碼,但全部部分都會加上詳細的中文註釋。另外,粘貼的代碼不會截取(某些不必的會刪除),這樣便於理解,而不用想看哪行代碼再去源碼中尋找了。
學習源碼的實現僅是咱們的目的之一,咱們更應該掌握做者優秀的編程思想,理解這樣作的初衷,站在更高的角度思考問題。
本系列文章的源碼所有基於JDK1.8,不一樣版本的實現代碼可能稍有差異,但核心思想是一致的,但願你們不要被具體的實現帶偏了路。
Java集合類分爲兩大部分:Collection和Map。Collection又主要由List、Queue和Set三大模塊組成。本系列文章也會基於這樣的結構進行,咱們會先了解一些用到的數據結構,而後按照從接口抽象到具體實現的順序來一步步揭開集合的神祕面紗。
因爲Set的結構與Map徹底一致,且Set的內部都是基於對應的Map實現的,因此只須要知道Set是什麼便可,其具體實現若是感興趣能夠自行閱讀源碼。
本系列文章不考慮多線程安全問題,與多線程相關的問題十分複雜,之後會對它專門研究。
本系列文章長達20多篇,所有讀完須要必定的耐心,可是我相信讀完對數據結構和集合必定會有更深的理解,在使用時須要注意哪些點也必定會成竹在胸。
另外因爲我的能力有限,文章中如有表達不清晰或解釋錯誤的部分,但願各位看官可以給予批評指正。
本系列文章會按照下述結構搭建:
數據結構
Iterable概述
Collection概述
List系列分析
Queue系列分析
Map概述與系列分析
Set簡介
Java集合源碼分析之基礎(五):平衡二叉樹(AVL Tree)
Java集合源碼分析之Queue(三):ArrayDeque
Java集合源碼分析之Map(三):接口NavigableMap
Java集合源碼分析之Map(六):LinkedHashMap
本系列文章所有更新完畢,感謝您的關注~
本文到此就結束了,若是您喜歡個人文章,能夠關注個人微信公衆號:大大紙飛機
或者掃描下方二維碼直接添加:
您也能夠關注個人github:https://github.com/LtLei/articles
編程之路,道阻且長。惟,路漫漫其修遠兮,吾將上下而求索。