閱讀源碼是提高編程能力的一項基礎技能,可是不少初學者在閱讀源碼過程當中不得其法,每每花費了大量的時間卻沒有收到預期的效果。或者在閱讀過程當中沒法瞭解做者的意圖,白白錯失了學習的機會,所以我但願藉助這個系列,經過解讀 Java Collections Framework 的源碼,給你們概括 Java Collections Framework 相關知識的同時也分享一些本身閱讀源碼的技巧。 java
對於 Java 開發者而言,現今是個幸福的時代,開源文化的盛行讓你們對於不少項目的源碼可謂唾手可得。而十幾年來 Java 技術體系的穩步發展,積累了許許多多基於 Java 語言的優秀開源框架。然而基礎對於軟件工程師的成長更爲重要,在想深刻某個框架以前,不如讓咱們把眼光轉向 JDK 自帶的源碼中,來學習一些基礎可是格外重要的知識。程序員
要想成爲一個合格的 Java 程序員,至少有兩個框架的知識和源碼是須要熟練掌握的,即 Java Collections Framework 和 Java Concurrency Framework,這個系列會帶着你們分析 Collections Framework 的源碼,從淺入深,抽絲剝繭的講述相關知識。編程
本系列分析的源碼以 OpenJDK 11 爲主,但願每一個讀者能夠在本機下載相關的源碼,在看完每篇文章後再本身仔細的閱讀相關的代碼,並作好筆記。設計模式
Java 是一門面向對象的語言,在使用中每每會採用繼承,Delegate,組合等多種手法,或者是各類設計模式。所以一開始閱讀源碼時容易在複雜的類層次以及調用關係中迷失,因此在一開始建議你們先看一下整體的類圖結構,瞭解大體的繼承體系,並進一步找出幾個核心的接口和實現類。而在閱讀源碼以前最好看看框架的文檔,並手動寫一些簡單的實例代碼,對框架的功能有一個大體的瞭解。下面是 Java Collections Framework 的類圖:數組
從類圖上來看 Java Collections Framework 分爲兩大部分,提供集合類操做接口的 Collection,和提供 Key-Value 接口訪問的 Map。Collection 接口衍生了以下 3 大類型的數據結構:安全
以後的文章會按照這個分類進行相關源碼的分析,本篇則會分析上層的 Collection 的相關接口。微信
在開始閱讀代碼以前,比較好的習慣是先看一下源碼的註釋。特別是 JDK 的源碼,自帶的註釋是很是詳細且有價值的,在註釋中不只解釋了當前類或是接口做用和設計意圖,還描述了相關 API 使用的注意事項等,對於理解源碼是頗有幫助的。數據結構
先來看一下 Collection 的註釋和方法簽名。Collection 表明了一系列元素的集合,做爲它的具體實現,能夠是各類不一樣的數據結構類型,也就是咱們以前提到的 List, Queue, Set 等類型。同時一個 Collection 類型的數據類型必須是「可迭代」的,即實現了 Iterable 接口,Iterable 接口最重要的方法是 iterator(),即返回一個 Iterator 對象。Iterator 對象上重要的方法分別爲 hasNext(), next(), remove() ,有必定經驗的開發人員在 JDK8 以前的編程中應該都有使用過這個接口及其相關的方法。多線程
從註釋中咱們還能夠知道,Collection 的相關接口並無保證線程安全性,須要開發人員顯式的添加各類機制來保證多線程下的數據一致性。框架
再來看一下 Collection 的各個方法簽名,都比較容易理解,你們應該在平常開發中都有用過,就不在這裏贅述了。
爲了方便開發人員實現自定義的集合類型數據結構,JDK 提供了一個對於 Collection 接口的抽象類實現: AbstractCollection ,對實現了部分 Collection 方法的實現。
從註釋上來看,有幾點是值得注意的。若是須要實現一個不可修改的(unmodifiable)的類型,那隻須要實現 size() 和 iterator() 方法便可。若是須要實現一個可修改的的類型,則須要額外實現 add() 和 迭代器的 remove() 方法。
從結構來看 AbstractCollection 採用了 Template Pattern ,即「模版模式」,經過實現某些抽象方法的來控制具體的行爲。同時在實現的方法方面,基本都是經過迭代器實現的,從而避免了耦合到具體的實現,參考以下代碼:
public boolean contains(Object o) { Iterator<E> it = iterator(); if (o==null) { while (it.hasNext()) if (it.next()==null) return true; } else { while (it.hasNext()) if (o.equals(it.next())) return true; } return false; } public boolean remove(Object o) { Iterator<E> it = iterator(); if (o==null) { while (it.hasNext()) { if (it.next()==null) { it.remove(); return true; } } } else { while (it.hasNext()) { if (o.equals(it.next())) { it.remove(); return true; } } } return false; }
其中比較有意思的方法是 toArray(T[] a) 這個方法,先看下源碼。
public <T> T[] toArray(T[] a) { /// Estimate size of array; be prepared to see more or fewer elements/ int size = size(); T[] r = a.length >= size ? a : (T[])java.lang.reflect.Array .newInstance(a.getClass().getComponentType(), size); Iterator<E> it = iterator(); for (int i = 0; i < r.length; i++) { if (! it.hasNext()) { /// fewer elements than expected/ if (a == r) { r[i] = null; /// null-terminate/ } else if (a.length < i) { return Arrays.copyOf(r, i); } else { System.arraycopy(r, 0, a, 0, i); if (a.length > i) { a[i] = null; } } return a; } r[i] = (T)it.next(); } /// more elements than expected/ return it.hasNext() ? finishToArray(r, it) : r; }
for
循環的段邏輯較爲容易理解,即當數組的長度大於集合數據類型長度時,會將額外部分用 null
來填充。須要注意的狀況是當集合數據類型長度大於數組長度時的處理方法。關鍵的方法是 finishToArray 方法。
@SuppressWarnings("unchecked") private static <T> T[] finishToArray(T[] r, Iterator<?> it) { int i = r.length; while (it.hasNext()) { int cap = r.length; if (i == cap) { int newCap = cap + (cap >> 1) + 1; /// overflow-conscious code/ if (newCap - MAX_ARRAY_SIZE > 0) newCap = hugeCapacity(cap + 1); r = Arrays.copyOf(r, newCap); } r[i++] = (T)it.next(); } /// trim if overallocated/ return (i == r.length) ? r : Arrays.copyOf(r, i); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) /// overflow/ throw new OutOfMemoryError ("Required array size too large"); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
能夠看到當數據長度小的時候,會按照必定的邏輯進行擴容,有個值得注意的技巧是位運算,即這行代碼 int newCap = cap + (cap >> 1) + 1;
每次會按照自身大小的 1/2 進行擴容,每次左移 1 位,等於除以 2,而除法運算的時間大於位移操做的消耗。這些位移技巧很是實用,在 JDK 中也比較常見,你們須要瞭解並熟悉。
而在數組擴容擴容的時候會調用一個 hugeCapacity() 的方法,這個方法的邏輯也很簡單,首先會考慮是否溢出的狀況,這在系統編程時是個很好的習慣,其次檢查了 MAX_ARRAY_SIZE
這個常量,能夠看到這個值的默認大小是 Integer.MAX_VALUE
減去 8 ,具體的緣由在註釋裏說起了,某些 JVM 的實現中數組對象可能會存放一些字頭,所以須要預留一些空間。
其餘的方法都較爲簡單,你們能夠耐心閱讀一下,應該很容易理解,就不贅述了。
本篇整體介紹了 Java Collections Framework 中類的層級關係,並着重介紹了 Collection, AbstractCollection 兩個類,並分析了幾個核心的方法。下一篇咱們會分析你們在平常編程中使用率最高的數據結構 List 和它的子類。
歡迎關注個人微信號「且把金針度與人」,獲取更多高質量文章