標籤:boolean 缺點 move 方式 複製 collect 判斷 排列 指定位置
1. Java集合架構圖
所示:Java有兩類集合,線性表(Collection子類)和索引值對(Map子類)
2. Java集合架構圖-2
3. 線性表
ArrayList、Vector的引入
l 數組在使用上有局限性:建立後無法改變儲存大小。
代碼示意:int[] iAry=new int[3]; iAry[0]=0;iAry[3];//err
l 此時可用動態數組類ArrayList或Vector:
ArrayList<Integer> ary=new ArrayList<Integer> ();
ary.add(0);…ary.add(3);
ary空間無限制,當空間無法容納時會重新開闢更大空間。
l ArrayList和Vector的異同:
相同點:
都是基於數組來實現的;都可從指定位置取得元素,從容器的末尾
增加和刪除元素都非常的有效,能在一個常數級的時間(O(1))內完成。但是
從一個其他的位置增加和刪除一個元素頗費時,需要的時間為O(n-i),這裡
的n代表元素個數,i代表要增加和刪除的元素所在的位置。
差別:
1、Vector法都是同步的,即安全執行緒,而ArrayList不考慮安全執行緒,ArrayList的效能比Vector好;
2、 當添加元素超容量時,Vector容量翻倍,而ArrayList增加50%(更節約記憶體空間)。 所以,在單線程裡,盡量用ArrayList。
l ArrayList的優缺點
以數組實現。節約空間,但數組有容量限制。超出限制時會增加50%容量,用System.arraycopy()複製到新的數組,因此最好能給出數組大小的預估值。預設第一次插入元素時建立大小為10的數組。
按數組下標訪問元素--get(i)/set(i,e) 的效能很高,這是數組的基本優勢。
直接在數組末尾加入元素--add(e)的效能也高,但如果按下標插入、刪除元素--add(i,e), remove(i), remove(e),則要用System.arraycopy()來移動部分受影響的元素,效能就變差了,這是基本劣勢。
l LinkedList的結構
l LinkedList的優缺點
以雙向鏈表實現。鏈表無容量限制,但雙向鏈表本身使用了更多空間,也需要額外的鏈表指標操作。
按下標訪問元素--get(i)/set(i,e) 要悲劇的遍曆鏈表將指標移動到位(如果i>數組大小的一半,會從末尾移起)。
插入、刪除元素時修改前後節點的指標即可,但還是要遍曆部分鏈表的指標才能移動到下標所指的位置,只有在鏈表兩頭的操作--add(), addFirst(),removeLast()或用iterator()上的remove()能省掉指標的移動。
l ArrayList與LinkedList的比較
ArrayList基於數組:便於索引,但不便於插入刪除(需要移動元素);
LinkedList基於鏈表:插入和刪除只需修改指標,但不能隨機存取(需從head找起)
代碼理解:
List<String> shenXiao = new LinkedList<String>(); shenXiao.add("子(zǐ)");
shenXiao.add("醜(chǒu)");
shenXiao.add("卯(mǎo)"); shenXiao.add("辰(chén)");
shenXiao.add("巳(sì)"); shenXiao.add("午(wǔ)");
shenXiao.add("未(wèi)"); shenXiao.add("申(shēn)");
shenXiao.add("酉(yǒu)"); shenXiao.add("戌(xū)");
shenXiao.add("亥(hài)"); shenXiao.add(2, "寅(yín)");
System.out.println(shenXiao);//自動遍曆[..寅(yín)..亥(hài)]
l Set
作為Collection介面的重要子介面,Set介面是一個不包含重複元素,且元素排列無序的集合,並且最多包含一個 null 元素,也被稱為集。
Set介面的方法和Collection介面的方法大體相同,但在遍曆時只能通過Iterator()方法擷取迭代器進行迭代。
Set中的add方法不同於Collection介面,有一個boolean類型的傳回值,當集合中含有與某個元素equals相等的元素時返回false,無法添加,否
則返回true。
Set中的Iterator方法返回的返回的元素沒有特定的順序(除非此 set 是某個提供順序保證的類的執行個體)。
l HashSet
HashSet類是Set介面的重要實作類別,由雜湊表(實際上是一個 HashMap 執行個體)支援。它不保證 set 的迭代順序;特別是它不保證該順序恒久不變。
Set介面是通過equals方法來比較兩元素是否相同的,但是如果有1000個元素,那麼再添加一個元素,就要比較1000次,效率嚴重降低。所以HashSet類底層採用了HashMap來儲存元素,而HashMap採用了雜湊表的原理。
因此HashSet類按照雜湊演算法來存取對象,當向集合中加入一個新對象時,會調用對象的HashCode()方法得到對象的雜湊碼,然後根據這個碼計算出對象在集合中儲存的位置。 HashCode()方法實際上是返回的Object Storage Service的物理地址。
所以總的來說,HashSet是通過兩元素的HashCode()方法和equals()方法來判斷兩元素是否相等的。
所以當我們使用HashSet添加元素時一定要重寫該元素的HashCode方法和equals方法。並且,參與計算兩個方法傳回值的關鍵屬性應該相同。 程式碼範例:HashSetDemo.java
l TreeSet
TreeSet是一個有序集合,TreeSet中的元素將按照升序排列,預設是按照自然排序進行排列,意味著TreeSet中的元素要實現Comparable介面。
在類上implement Comparable重寫compareTo()方法,在方法內定義比較演算法, 根據大小關係, 返回正數負數或零,在使用TreeSet儲存物件的時候, add()方法內部就會自動調用compareTo()方法進行比較。
代碼:自訂類做元素
必須實現Comparable,否則TreeSet無法add()
l HashMap
對於 HashMap 及其子類而言,它們採用 Hash 演算法來決定集合中元素的儲存位置。
當系統開始初始化 HashMap 時,系統會建立一個Entry 數組,這個數組裡可以儲存元素的位置被稱為“桶(bucket)”,每個 bucket 都有其指定索引,系統可以根據其索引快速存取該 bucket 裡儲存的元素。
無論何時,HashMap 的每個“桶”只儲存一個元素(也就是一個 Entry),由於Entry 對象可以包含一個引用變數(就是 Entry 構造器的的最後一個參數)用於指向下一個 Entry,因此可能出現的情況是:HashMap 的 bucket 中只有一個Entry,但這個 Entry 指向另一個 Entry ——這就形成了一個 Entry 鏈。:
l TreeMap和HashMap的差別
HashMap通過hashcode對其內容進行快速尋找;而TreeMap基於紅/黑樹狀結構實現的,其所有的元素都保持著某種固定的順序,如自然順序或自訂順序。
在Map中插入、刪除和定位元素,HashMap是最好的選擇。但如果要按自然順序或自訂順序遍曆鍵,那麼TreeMap會更好。
註:
1.同樣的對比有TreeSet和HashSet,因為TreeSet基於TreeMap, HashSet基於HashMap
2.使用HashMap要求添加的鍵類需明確實現hashCode()、 equals()。
l 索引值對-Hash演算法
Hash表引入原因:數組定址容易,插入和刪除困難; 而鏈表定址困難,插入和刪除容易。雜湊表可綜合兩者的優點,即,定址容易,插入刪除也容易的資料結構。
Hash(散列)演算法,把任意長度的輸入,用演算法函數變換成固定長度的輸出 (散列值)。不同的輸入可能會散列成相同散列值。
Hash表有多種實現,以拉鏈法(鏈表的數組)解釋: ? 左邊數組,數組每個元素中 包括一個指標,指向一個鏈表的頭。根據元素的一些特徵把元素分配到不同的鏈表中去,也是根據這些特徵,找到正確的鏈表,再從鏈表中找出這個元素。
散列法:元素特徵轉變為數組下標的方法就是散列法,使用除法散列法:index = value % 13
索引值對-Hash演算法 HashCode的理解
Hashcode(散列碼):每個對象都會有一個int型的hashcode,代表它在記憶體中的位置。
Object中的equals方法是指比較兩個對象是不是指向同一個引用。hashcode方法返回hashCode的值;Object規範裡規定:如果兩個對象根據equals(Object)方式是相等的,那麼這兩個對象的hashCode一定要相等,所以記住:重寫equals方法後,請重寫hashCode方法。
l 索引值對
HashMap和Hashtable
Hashtable繼承自Dictionary類,而HashMap是Map介面的一個實現。
代碼:(HashMap改為Hashtable,練習)
主要差別:
1 HashMap允許null key和null value,而hashtable不允許;
2 HashMap是Hashtable的輕量級實現(非安全執行緒的實現),效率高;
3 空間:載入因子預設都為0.75;初始容量Hashtable為11,而HashMap為16。 增加方式Hashtable為 oldSize*2+1,HashMap為oldSize *2。
l 列舉程式訪問集合對象
Iterator模式是用於遍曆集合類的標準存取方法。它可以把訪問邏輯從不同類型的集合類中抽象出來,從而避免向用戶端暴露集合的內部結構。
例如,如果沒有使用Iterator,遍曆一個數組的方法是使用索引:
for(int i=0; i<array.size(); i++) { ... get(i) ... } 而訪問一個鏈表(LinkedList)又必須使用while迴圈:while((e=e.next())!=null) { ... e.data() ... } 以上兩種方法用戶端都必須事Crowdsourced Security Testing道集合的內部結構,存取碼和集合本身是緊耦合。無法將訪問邏輯從集合類和用戶端代碼中分離出來,每一種集合對應一種遍曆方法,用戶端代碼無法複用。更恐怖的是,如果以後需要把ArrayList更換為LinkedList,則原來的用戶端代碼必須全部重寫。
為解決以上問題,Iterator模式總是用同一種邏輯來遍曆集合:
for(Iterator it = c.iterater(); it.hasNext(); ) { ... } 這樣一來用戶端自身不維護遍曆集合的"指標",所有的內部狀態(如當前元素位置,是否有下一個元素)都由Iterator來維護,而這個Iterator由集合類通過Factory 方法產生,因此,它知道如何遍曆整個集合。
用戶端從不直接和集合類打交道,它總是控制Iterator,向它發送"向前","向後","取
通過列舉程式訪問線性集合
通過列舉程式訪問HashMap
泛型
泛型:即參數化型別,所操作的資料類型被指定為一個參數。 ?
這種參數類型可以用在類、介面和方法的建立中(即,泛型類、泛型介面、
泛型方法)。
泛型好處:編譯時間可檢測類型錯誤;避免了強制類型轉換(自動和隱式 ?
的),提高代碼的重用性。
例:
以上的List就是泛型介面、ArrayList就是泛型類、 add()和get()是泛型方法
註:集合架構,都已經成為泛型化了。
泛型的規則
泛型定義代碼舉例
類型萬用字元?引入原因:
泛型的繼承,代碼說明:
l 淺拷貝與Clone
有時我們需要複製對象:
測試:
l 淺拷貝與Clone(續)
分析,從上面代碼運行結果看:
成員變數為基本類型(外加String類型—內容不變性),只要super.clone()就可
拷貝。但數組或複雜類型(參考型別)時,就必須實現深度clone。
l 深度clone(本例):
l 深度clone()
容器類成員,需逐個元素複製,以ArrayList為例
java 集合類