JavaSE中Collection集合架構學習筆記(1)——具有索引的List,javasecollection
前言:因為最近要重新找工作,Collection(集合)是面試中出現頻率非常高的基礎考察點,所以好好惡補了一番。
複習過程中深感之前的學習不系統,而且不能再像剛畢業那樣死背面試題,例如:String是固定長度的,StringBuffer和StringBuilder的長度是可以變化的。如果一旦問得深入一點,問為什麼有這樣的區別就傻眼了,只能一臉呆萌地看著面試官。
因此想要通過寫文章的形式,系統地總結學習的內容,例如Collection架構是怎樣的、有哪些相關的繼承和介面實現,這樣才能瞭解什麼時候應該用哪個類,以及類之間要如何搭配合作,才知道出了問題應該如何解決。這一系列文章適用於Java技術崗的應聘者、高校電腦專業的學生以及培訓機構學習Java的初學者閱讀。
1.1 認識Collection架構
我們都使用過ArrayList類收集對象,例如add()方法新增對象,remove()方法移除對象,這些都不會陌生。但是這些方法是怎麼來的呢?是該類的繼承架構圖:
ArrayList一個類就這麼複雜,如果要把全部Collection架構表現在一張圖上,那估計就跟蜘蛛網一樣糾纏不清。簡化一下,忽略一些不那麼重要的介面和實作類別,我們可以得到以下這張架構圖。
從圖上可知,Colletion是一個介面,實現了另一個介面Iterable。Collection下面有三個介面直接實現了它,分別是List、Set和Queue。List下面有兩個實作類別,分別是ArrayList和LinkedList;Set的常用實作類別是TreeSet和HashSet;Queue下面有Deque介面實現,再下面是實作類別ArrayDeque.
這張圖將會是這一系列文章的核心,之後會反覆提及,不妨稱之為Collection架構圖,每一篇文章都是介紹其中的一部分。熟悉這張圖,不僅有助於理解學習,還可以協助記憶。至於詳細周全的繼承關係和實現架構,到底有哪些類實現了哪些介面、繼承了哪些類,可以自行在API說明文檔中查詢。
1.2 具有索引的List
List實現了Collection介面,所以我們可以說List是一種Colletion,作用就是收集對象,特點是以索引的方式記錄所收集的對象順序。List中常見的實作類別是剛才所提及架構圖中的ArrayList,忘了的讀者可以翻到前面對照著看。
1 /** 2 * ArrayList的實驗用例 3 */ 4 5 import java.util.*; 6 7 public class Student { 8 public static void main(String[] args) { 9 List list = new ArrayList(); //使用JavaSE的List和ArrayList10 Scanner scanner = new Scanner(System.in);11 String name;12 while(true) {13 System.out.print("學生簽到:");14 name = scanner.nextLine();15 if(name.equals("quit")) {16 break;17 }18 list.add(name); //實用Add()方法收集對象19 }20 System.out.println("今天上來上課的學生名單:");21 foreach(list);22 }23 24 private static void foreach(List list) {25 for(int i = 0; i < list.size(); i++) {26 String student = (String) list.get(i); //使用get()方法依據索引取得收集的對象27 System.out.println(student);28 }29 }30 }
以上是ArrayList類的一個簡單使用例子,類比的是學生上課簽到的情景。強烈建議讀者跟我一樣自己試著寫一個簡單用例,尤其是之前很少使用ArrayList的初學者,單純的看和讀跟實際敲代碼產生的效果完全不一樣。也可以照著我給出的例子敲,偷懶一點的話可以直接複製在機器上跑一遍。
從Collection架構圖中可知,LinkedList同樣也實現了List介面。就算只是把上面那個實驗中的ArrayList全部改為LinkedList,程式照樣可以運作,而且效果看起來完全相同。那麼問題來了,我們什麼時候應該使用ArrayList,什麼時候又應該使用LinkedList呢?
1.2.1 ArrayList的特性
卡車和輪船都可以運送貨物,我們可以根據不同的情況選擇不同的運輸方式。如果時間緊、運輸量小,而且兩個地點都在陸地上(例如北京到南京),那麼我們可以使用汽車;如果時間多、運輸量大,出發地和目的之間隔著海洋(例如大連到紐約),那麼用船運是更好的選擇。
剛畢業那會要找工作,為了面試背過“ArrayList像數組,讀取速度快,但是需要調整索引的話表現很差;LinkedList像鏈表,調整索引的表現非常好,但是隨機讀取的速度比較慢”。那麼我們可以問深一句,為什麼會這樣呢?不妨從原始碼中找尋答案。
1 public boolean add(E e) {2 ensureCapacityInternal(size + 1); // Increments modCount!!3 elementData[size++] = e;4 return true;5 }
上面這一段是JavaSE的原始碼,我們可以看到ArrayList中的add()方法非常簡單,跟我們平時使用數組一樣。查看原始碼中更多內容你會發現,ArrayList內部就是使用Object數組來儲存所收集的對象,這就是為什麼說“ArrayList就像數組”的原因。在考慮是否使用ArrayList的時候,我們可以相當於考慮是否要使用數組的特性。
1.2.2 LinkedList的特性
在學習Collection架構的時候,我們不妨可以多看原始碼,看的時候優先比較幾個基本方法的實現,例如add()、remove()等。從這些方法的實現,我們就可以看到不同實作類別的特性。
public boolean add(E e) { linkLast(e); return true; } /** * Links e as last element. */ void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; }
看到LinkdedList.add()的原始碼,我們會發現其實現方式跟鏈表的實現如出一轍。如果last結點為null,那麼說明鏈表為空白,所以新添加的結點為頭結點。如果last結點不等於null,那麼把新添加的結點設為last的下一個結點,作為新的尾結點。
根據鏈表的特性,我們可以很快總結兩點點特性:1.想要指定索引隨機存取時,連結方式都得使用從第一個元素開始尋找下一個元素的方式,效率比較糟糕;2.連結的每個元素都會參考下一個元素,這有利於調整索引順序。
1.2.3 List總結
作為Collection三大陣營之一的List,最大的特點就是索引,我們可以通過索引做到隨機存取。
List中常用的實現有ArrayList和LinkedList,各自的特性可以分別參考數組和鏈表。在比較它們之間區別的過程中,我們看了原始碼,提倡在比較同一介面不同實作類別時重點查看它們共同需要實現的方法,例如Collection中規定的add(),remove()等。
面試中常見的List實作類別其實還有Vector,其特性與ArrayList相同。不同在於Vector具有安全執行緒的特性,效能開銷比較大,具體的內容會放在以後關於多線程的文章裡。
在總結過程中,給初學者提供兩個建議,一是做實驗,通過寫一些demo來熟悉所學內容;二是在力所能及的情況下多看原始碼,知其然也要知其所以然。
如果你喜歡我的文章,可以掃描關注我的個人公眾號“李文業的思考筆記”。
不定期地會推送我的原創思考文章。