←←←←←←←←←←←← 快!點關注java
簡化圖:算法
說明:對於以上的框架圖有以下幾點說明數組
Set、List和Map能夠看作集合的三大類:安全
大體說明:數據結構
看上面的框架圖,先抓住它的主幹,即Collection和Map。框架
Collection是一個接口,是高度抽象出來的集合,它包含了集合的基本操做和屬性。Collection包含了List和Set兩大分支。函數
有了上面的總體框架以後,咱們接下來對每一個類分別進行分析。
Collection接口是處理對象集合的根接口,其中定義了不少對元素進行操做的方法。Collection接口有兩個主要的子接口List和Set,注意Map不是Collection的子接口,這個要牢記。工具
Collection接口中的方法以下:性能
其中,有幾個比較經常使用的方法,好比方法add()添加一個元素到集合中,addAll()
將指定集合中的全部元素添加到集合中,contains()方法檢測集合中是否包含指定的元素,toArray()
方法返回一個表示集合的數組。測試
另外,Collection
中有一個iterator()
函數,它的做用是返回一個Iterator
接口。一般,咱們經過Iterator
迭代器來遍歷集合。ListIterator
是List接口所特有的,在List接口中,經過ListIterator()
返回一個ListIterator
對象。
Collection接口有兩個經常使用的子接口,下面詳細介紹。
List集合表明一個有序集合,集合中每一個元素都有其對應的順序索引。List集合容許使用重複元素,能夠經過索引來訪問指定位置的集合元素。
List接口繼承於Collection接口,它能夠定義一個容許重複的有序集合。由於List中的元素是有序的,因此咱們能夠經過使用索引(元素在List中的位置,相似於數組下標)來訪問List中的元素,這相似於Java的數組。
List接口爲Collection直接接口。List所表明的是有序的Collection,即它用某種特定的插入順序來維護元素順序。用戶能夠對列表中每一個元素的插入位置進行精確地控制,同時能夠根據元素的整數索引(在列表中的位置)訪問元素,並搜索列表中的元素。實現List接口的集合主要有:ArrayList、LinkedList、Vector、Stack
。
ArrayList是一個動態數組,也是咱們最經常使用的集合。它容許任何符合規則的元素插入甚至包括null。每個ArrayList都有一個初始容量(10),該容量表明瞭數組的大小。隨着容器中的元素不斷增長,容器的大小也會隨着增長。在每次向容器中增長元素的同時都會進行容量檢查,當快溢出時,就會進行擴容操做。因此若是咱們明確所插入元素的多少,最好指定一個初始容量值,避免過多的進行擴容操做而浪費時間、效率。
size、isEmpty、get、set、iterator和 listIterator
操做都以固定時間運行。add 操做以分攤的固定時間運行,也就是說,添加 n 個元素須要 O(n) 時間(因爲要考慮到擴容,因此這不僅是添加元素會帶來分攤固定時間開銷那樣簡單)。
ArrayList擅長於隨機訪問。同時ArrayList是非同步的。
一樣實現List接口的LinkedList與ArrayList不一樣,ArrayList是一個動態數組,而LinkedList是一個雙向鏈表。因此它除了有ArrayList的基本操做方法外還額外提供了get,remove,insert方法在LinkedList的首部或尾部。
因爲實現的方式不一樣,LinkedList不能隨機訪問,它全部的操做都是要按照雙重鏈表的須要執行。在列表中索引的操做將從開頭或結尾遍歷列表(從靠近指定索引的一端)。這樣作的好處就是能夠經過較低的代價在List中進行插入和刪除操做。
與ArrayList同樣,LinkedList也是非同步的。若是多個線程同時訪問一個List,則必須本身實現訪問同步。一種解決方法是在建立List時構造一個同步的List:
List list = Collections.synchronizedList(new LinkedList(...));
與ArrayList類似,可是Vector是同步的。因此說Vector是線程安全的動態數組。它的操做與ArrayList幾乎同樣。
Stack繼承自Vector,實現一個後進先出的堆棧。Stack提供5個額外的方法使得Vector得以被看成堆棧使用。基本的push和pop 方法,還有peek方法獲得棧頂的元素,empty方法測試堆棧是否爲空,search方法檢測一個元素在堆棧中的位置。Stack剛建立後是空棧。
Set是一種不包括重複元素的Collection。它維持它本身的內部排序,因此隨機訪問沒有任何意義。與List同樣,它一樣容許null的存在可是僅有一個。因爲Set接口的特殊性,全部傳入Set集合中的元素都必須不一樣,同時要注意任何可變對象,若是在對集合中元素進行操做時,致使e1.equals(e2)==true
,則一定會產生某些問題。Set接口有三個具體實現類,分別是散列集HashSet、鏈式散列集LinkedHashSet和樹形集TreeSet。
Set是一種不包含重複的元素的Collection,無序,即任意的兩個元素e1和e2都有e1.equals(e2)=false,Set最多有一個null元素。
須要注意的是:雖然Set中元素沒有順序,可是元素在set中的位置是由該元素的HashCode決定的,其具體位置實際上是固定的。
此外須要說明一點,在set接口中的不重複是有特殊要求的。
舉一個例子:對象A和對象B,原本是不一樣的兩個對象,正常狀況下它們是可以放入到Set裏面的,可是若是對象A和B的都重寫了hashcode和equals方法,而且重寫後的hashcode和equals方法是相同的話。那麼A和B是不能同時放入到Set集合中去的,也就是Set集合中的去重和hashcode與equals方法直接相關。
爲了更好地理解,請看下面的例子:
public class Test{ public static void main(String[] args) { Set<String> set=new HashSet<String>(); set.add("Hello"); set.add("world"); set.add("Hello"); System.out.println("集合的尺寸爲:"+set.size()); System.out.println("集合中的元素爲:"+set.toString()); } }
運行結果:
集合的尺寸爲:2 集合中的元素爲:[world, Hello]
分析:因爲String類中重寫了hashcode和equals方法,用來比較指向的字符串對象所存儲的字符串是否相等。因此這裏的第二個Hello是加不進去的。
再看一個例子:
public class TestSet { public static void main(String[] args){ Set<String> books = new HashSet<String>(); //添加一個字符串對象 books.add(new String("Struts2權威指南")); //再次添加一個字符串對象, //由於兩個字符串對象經過equals方法比較相等,因此添加失敗,返回false boolean result = books.add(new String("Struts2權威指南")); System.out.println(result); //下面輸出看到集合只有一個元素 System.out.println(books); } }
運行結果:
false [Struts2權威指南]
說明:程序中,book集合兩次添加的字符串對象明顯不是一個對象(程序經過new關鍵字來建立字符串對象),當使用==運算符判斷返回false,使用equals方法比較返回true,因此不能添加到Set集合中,最後只能輸出一個元素。
HashSet 是一個沒有重複元素的集合。它是由HashMap實現的,不保證元素的順序(這裏所說的沒有順序是指:元素插入的順序與輸出的順序不一致),並且HashSet容許使用null 元素。HashSet是非同步的,若是多個線程同時訪問一個哈希set,而其中至少一個線程修改了該set,那麼它必須保持外部同步。 HashSet按Hash算法來存儲集合的元素,所以具備很好的存取和查找性能。
HashSet的實現方式大體以下,經過一個HashMap存儲元素,元素是存放在HashMap的Key中,而Value統一使用一個Object對象。
HashSet使用和理解中容易出現的誤區:
Mutable Object
)。若是一個Set中的可變元素改變了自身狀態致使Object.equals(Object)=true
將致使一些問題。LinkedHashSet繼承自HashSet,其底層是基於LinkedHashMap來實現的,有序,非同步。LinkedHashSet集合一樣是根據元素的hashCode值來決定元素的存儲位置,可是它同時使用鏈表維護元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。
TreeSet是一個有序集合,其底層是基於TreeMap實現的,非線程安全。TreeSet能夠確保集合元素處於排序狀態。TreeSet支持兩種排序方式,天然排序和定製排序,其中天然排序爲默認的排序方式。當咱們構造TreeSet時,若使用不帶參數的構造函數,則TreeSet的使用天然比較器;若用戶須要使用自定義的比較器,則須要使用帶比較器的參數。
注意:TreeSet集合不是經過hashcode和equals函數來比較元素的.它是經過compare或者comparaeTo函數來判斷元素是否相等.compare函數經過判斷兩個對象的id,相同的id判斷爲重複元素,不會被加入到集合中。
Map與List、Set接口不一樣,它是由一系列鍵值對組成的集合,提供了key到Value的映射。同時它也沒有繼承Collection。在Map中它保證了key與value之間的一一對應關係。也就是說一個key對應一個value,因此它不能存在相同的key值,固然value值能夠相同。
以哈希表數據結構實現,查找對象時經過哈希函數計算其位置,它是爲快速查詢而設計的,其內部定義了一個hash表數組(Entry[] table),元素會經過哈希轉換函數將元素的哈希地址轉換成數組中存放的索引,若是有衝突,則使用散列鏈表的形式將全部相同哈希地址的元素串起來,可能經過查看HashMap.Entry的源碼它是一個單鏈表結構。
LinkedHashMap是HashMap的一個子類,它保留插入的順序,若是須要輸出的順序和輸入時的相同,那麼就選用LinkedHashMap。
LinkedHashMap是Map接口的哈希表和連接列表實現,具備可預知的迭代順序。此實現提供全部可選的映射操做,並容許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恆久不變。
LinkedHashMap實現與HashMap的不一樣之處在於,後者維護着一個運行於全部條目的雙重連接列表。此連接列表定義了迭代順序,該迭代順序能夠是插入順序或者是訪問順序。
根據鏈表中元素的順序能夠分爲:按插入順序的鏈表,和按訪問順序(調用get方法)的鏈表。默認是按插入順序排序,若是指定按訪問順序排序,那麼調用get方法後,會將此次訪問的元素移至鏈表尾部,不斷訪問能夠造成按訪問順序排序的鏈表。
注意,此實現不是同步的。若是多個線程同時訪問連接的哈希映射,而其中至少一個線程從結構上修改了該映射,則它必須保持外部同步。因爲LinkedHashMap須要維護元素的插入順序,所以性能略低於HashMap的性能,但在迭代訪問Map裏的所有元素時將有很好的性能,由於它以鏈表來維護內部順序。
TreeMap 是一個有序的key-value集合,非同步,基於紅黑樹(Red-Black tree)實現,每個key-value節點做爲紅黑樹的一個節點。TreeMap存儲時會進行排序的,會根據key來對key-value鍵值對進行排序,其中排序方式也是分爲兩種,一種是天然排序,一種是定製排序,具體取決於使用的構造方法。
天然排序:TreeMap中全部的key必須實現Comparable接口,而且全部的key都應該是同一個類的對象,不然會報ClassCastException異常。
定製排序:定義TreeMap時,建立一個comparator對象,該對象對全部的treeMap中全部的key值進行排序,採用定製排序的時候不須要TreeMap中全部的key必須實現Comparable接口。
TreeMap判斷兩個元素相等的標準:兩個key經過compareTo()
方法返回0,則認爲這兩個key相等。
若是使用自定義的類來做爲TreeMap中的key值,且想讓TreeMap可以良好的工做,則必須重寫自定義類中的equals()
方法,TreeMap中判斷相等的標準是:兩個key經過equals()
方法返回爲true,而且經過compareTo()
方法比較應該返回爲0。
Iterator的定義以下:
public interface Iterator<E> {}
Iterator是一個接口,它是集合的迭代器。集合能夠經過Iterator去遍歷集合中的元素。
void remove():刪除集合裏上一次next方法返回的元素。
使用示例:
public class IteratorExample { public static void main(String[] args) { ArrayList<String> a = new ArrayList<String>(); a.add("aaa"); a.add("bbb"); a.add("ccc"); System.out.println("Before iterate : " + a); Iterator<String> it = a.iterator(); while (it.hasNext()) { String t = it.next(); if ("bbb".equals(t)) { it.remove(); } } System.out.println("After iterate : " + a); } }
輸出結果以下:
Before iterate : [aaa, bbb, ccc] After iterate : [aaa, ccc]
注意:
ListIterator是一個功能更增強大的迭代器, 它繼承於Iterator接口,只能用於各類List類型的訪問。能夠經過調用listIterator()
方法產生一個指向List開始處的ListIterator, 還能夠調用listIterator(n)
方法建立一個一開始就指向列表索引爲n的元素處的ListIterator。
ListIterator接口定義以下:
public interface ListIterator<E> extends Iterator<E> { boolean hasNext(); E next(); boolean hasPrevious(); E previous(); int nextIndex(); int previousIndex(); void remove(); void set(E e); void add(E e); }
由以上定義咱們能夠推出ListIterator能夠:
使用示例:
public class ListIteratorExample { public static void main(String[] args) { ArrayList<String> a = new ArrayList<String>(); a.add("aaa"); a.add("bbb"); a.add("ccc"); System.out.println("Before iterate : " + a); ListIterator<String> it = a.listIterator(); while (it.hasNext()) { System.out.println(it.next() + ", " + it.previousIndex() + ", " + it.nextIndex()); } while (it.hasPrevious()) { System.out.print(it.previous() + " "); } System.out.println(); it = a.listIterator(1); while (it.hasNext()) { String t = it.next(); System.out.println(t); if ("ccc".equals(t)) { it.set("nnn"); } else { it.add("kkk"); } } System.out.println("After iterate : " + a); } }
輸出結果以下:
Before iterate : [aaa, bbb, ccc] aaa, 0, 1 bbb, 1, 2 ccc, 2, 3 ccc bbb aaa bbb ccc After iterate : [aaa, bbb, kkk, nnn]
這一點要看實際狀況的。若只對單條數據插入或刪除,ArrayList的速度反而優於LinkedList。但如果批量隨機的插入刪除數據,LinkedList的速度大大優於ArrayList. 由於ArrayList每插入一條數據,要移動插入點及以後的全部數據。
相同點:
不一樣點:
基類不一樣:HashMap繼承於AbstractMap,而Hashtable繼承於Dictionary。
Hashmap 是一個最經常使用的Map,它根據鍵的HashCode 值存儲數據,根據鍵能夠直接獲取它的值,具備很快的訪問速度。遍歷時,取得數據的順序是徹底隨機的。HashMap最多隻容許一條記錄的鍵爲Null;容許多條記錄的值爲Null;HashMap不支持線程的同步,即任一時刻能夠有多個線程同時寫HashMap;可能會致使數據的不一致。若是須要同步,能夠用Collections的synchronizedMap方法使HashMap具備同步的能力。
Hashtable 與 HashMap相似,不一樣的是:它不容許記錄的鍵或者值爲空;它支持線程的同步,即任一時刻只有一個線程能寫Hashtable,所以也致使了Hashtale在寫入時會比較慢。
LinkedHashMap保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先獲得的記錄確定是先插入的,也能夠在構造時用帶參數,按照應用次數排序。在遍歷的時候會比HashMap慢,不過有種狀況例外,當HashMap容量很大,實際數據較少時,遍歷起來可能會比LinkedHashMap慢,由於LinkedHashMap的遍歷速度只和實際數據有關,和容量無關,而HashMap的遍歷速度和他的容量有關。
若是須要輸出的順序和輸入的相同,那麼用LinkedHashMap能夠實現,它還能夠按讀取順序來排列,像鏈接池中能夠應用。LinkedHashMap實現與HashMap的不一樣之處在於,後者維護着一個運行於全部條目的雙重鏈表。此連接列表定義了迭代順序,該迭代順序能夠是插入順序或者是訪問順序。對於LinkedHashMap而言,它繼承與HashMap、底層使用哈希表與雙向鏈表來保存全部元素。其基本操做與父類HashMap類似,它經過重寫父類相關的方法,來實現本身的連接列表特性。
TreeMap實現SortMap接口,內部實現是紅黑樹。可以把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也能夠指定排序的比較器,當用Iterator 遍歷TreeMap時,獲得的記錄是排過序的。TreeMap不容許key的值爲null。非同步的。
通常狀況下,咱們用的最多的是HashMap,HashMap裏面存入的鍵值對在取出的時候是隨機的,它根據鍵的HashCode值存儲數據,根據鍵能夠直接獲取它的值,具備很快的訪問速度。在Map 中插入、刪除和定位元素,HashMap 是最好的選擇。
TreeMap取出來的是排序後的鍵值對。但若是您要按天然順序或自定義順序遍歷鍵,那麼TreeMap會更好。
LinkedHashMap 是HashMap的一個子類,若是須要輸出的順序和輸入的相同,那麼用LinkedHashMap能夠實現,它還能夠按讀取順序來排列,像鏈接池中能夠應用。
import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.TreeMap; public class MapTest { public static void main(String[] args) { //HashMap HashMap<String,String> hashMap = new HashMap(); hashMap.put("4", "d"); hashMap.put("3", "c"); hashMap.put("2", "b"); hashMap.put("1", "a"); Iterator<String> iteratorHashMap = hashMap.keySet().iterator(); System.out.println("HashMap-->"); while (iteratorHashMap.hasNext()){ Object key1 = iteratorHashMap.next(); System.out.println(key1 + "--" + hashMap.get(key1)); } //LinkedHashMap LinkedHashMap<String,String> linkedHashMap = new LinkedHashMap(); linkedHashMap.put("4", "d"); linkedHashMap.put("3", "c"); linkedHashMap.put("2", "b"); linkedHashMap.put("1", "a"); Iterator<String> iteratorLinkedHashMap = linkedHashMap.keySet().iterator(); System.out.println("LinkedHashMap-->"); while (iteratorLinkedHashMap.hasNext()){ Object key2 = iteratorLinkedHashMap.next(); System.out.println(key2 + "--" + linkedHashMap.get(key2)); } //TreeMap TreeMap<String,String> treeMap = new TreeMap(); treeMap.put("4", "d"); treeMap.put("3", "c"); treeMap.put("2", "b"); treeMap.put("1", "a"); Iterator<String> iteratorTreeMap = treeMap.keySet().iterator(); System.out.println("TreeMap-->"); while (iteratorTreeMap.hasNext()){ Object key3 = iteratorTreeMap.next(); System.out.println(key3 + "--" + treeMap.get(key3)); } } }
輸出結果:
HashMap--> 3--c 2--b 1--a 4--d LinkedHashMap--> 4--d 3--c 2--b 1--a TreeMap--> 1--a 2--b 3--c 4--d
Set不容許包含相同的元素,若是試圖把兩個相同元素加入同一個集合中,add方法返回false。
Set判斷兩個對象相同不是使用==運算符,而是根據equals方法。也就是說,只要兩個對象用equals方法比較返回true,Set就不會接受這兩個對象。
HashSet有如下特色:
當向HashSet結合中存入一個元素時,HashSet會調用該對象的hashCode()方法來獲得該對象的hashCode值,而後根據 hashCode值來決定該對象在HashSet中存儲位置。簡單的說,HashSet集合判斷兩個元素相等的標準是兩個對象經過equals方法比較相等,而且兩個對象的hashCode()方法返回值也相等。
注意,若是要把一個對象放入HashSet中,重寫該對象對應類的equals方法,也應該重寫其hashCode()方法。其規則是若是兩個對象經過equals方法比較返回true時,其hashCode也應該相同。另外,對象中用做equals比較標準的屬性,都應該用來計算 hashCode的值。
LinkedHashSet集合一樣是根據元素的hashCode值來決定元素的存儲位置,可是它同時使用鏈表維護元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。
LinkedHashSet在迭代訪問Set中的所有元素時,性能比HashSet好,可是插入時性能稍微遜色於HashSet。
TreeSet是SortedSet接口的惟一實現類,TreeSet能夠確保集合元素處於排序狀態。TreeSet支持兩種排序方式,天然排序和定製排序,其中天然排序爲默認的排序方式。向TreeSet中加入的應該是同一個類的對象。
TreeSet判斷兩個對象不相等的方式是兩個對象經過equals方法返回false,或者經過CompareTo方法比較沒有返回0。
天然排序使用要排序元素的CompareTo(Object obj)
方法來比較元素之間大小關係,而後將元素按照升序排列。
Java提供了一個Comparable接口,該接口裏定義了一個compareTo(Object obj)方法,該方法返回一個整數值,實現了該接口的對象就能夠比較大小。obj1.compareTo(obj2)方法若是返回0,則說明被比較的兩個對象相等,若是返回一個正數,則代表obj1大於obj2,若是是負數,則代表obj1小於obj2。若是咱們將兩個對象的equals方法老是返回true,則這兩個對象的compareTo方法返回應該返回0。
天然排序是根據集合元素的大小,以升序排列,若是要定製排序,應該使用Comparator接口,實現 int compare(T o1,T o2)
方法。
package com.test; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.TreeSet; /** * @description 幾個set的比較 * HashSet:哈希表是經過使用稱爲散列法的機制來存儲信息的,元素並無以某種特定順序來存放; * LinkedHashSet:以元素插入的順序來維護集合的連接表,容許以插入的順序在集合中迭代; * TreeSet:提供一個使用樹結構存儲Set接口的實現,對象以升序順序存儲,訪問和遍歷的時間很快。 * @author Zhou-Jingxian * */ public class SetDemo { public static void main(String[] args) { HashSet<String> hs = new HashSet<String>(); hs.add("B"); hs.add("A"); hs.add("D"); hs.add("E"); hs.add("C"); hs.add("F"); System.out.println("HashSet 順序:\n"+hs); LinkedHashSet<String> lhs = new LinkedHashSet<String>(); lhs.add("B"); lhs.add("A"); lhs.add("D"); lhs.add("E"); lhs.add("C"); lhs.add("F"); System.out.println("LinkedHashSet 順序:\n"+lhs); TreeSet<String> ts = new TreeSet<String>(); ts.add("B"); ts.add("A"); ts.add("D"); ts.add("E"); ts.add("C"); ts.add("F"); System.out.println("TreeSet 順序:\n"+ts); } }
輸出結果:
HashSet 順序:[D, E, F, A, B, C] LinkedHashSet 順序:[B, A, D, E, C, F] TreeSet 順序:[A, B, C, D, E, F]
咱們在使用List,Set的時候,爲了實現對其數據的遍歷,咱們常用到了Iterator(迭代器)。使用迭代器,你不須要干涉其遍歷的過程,只須要每次取出一個你想要的數據進行處理就能夠了。可是在使用的時候也是有不一樣的。
List和Set都有iterator()來
取得其迭代器。對List來講,你也能夠經過listIterator()取得其迭代器,兩種迭代器在有些時候是不能通用的,Iterator和ListIterator主要區別在如下方面:
add()
方法,能夠向List中添加對象,而Iterator不能hasNext()
和next()
方法,能夠實現順序向後遍歷,可是ListIterator有hasPrevious()
和previous()
方法,能夠實現逆向(順序向前)遍歷。Iterator就不能夠。nextIndex()
和previousIndex()
能夠實現。Iterator沒有此功能。set()
方法能夠實現。Iierator僅能遍歷,不能修改。由於ListIterator的這些功能,能夠實現對LinkedList等List數據結構的操做。其實,數組對象也能夠用迭代器來實現。
(1)java.util.Collection
是一個集合接口(集合類的一個頂級接口)。它提供了對集合對象進行基本操做的通用接口方法。Collection接口在Java 類庫中有不少具體的實現。Collection接口的意義是爲各類具體的集合提供了最大化的統一操做方式,其直接繼承接口有List與Set。
Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set
(2)java.util.Collections 是一個包裝類(工具類/幫助類)。它包含有各類有關集合操做的靜態多態方法。此類不能實例化,就像一個工具類,用於對集合中元素進行排序、搜索以及線程安全等各類操做,服務於Java的Collection框架。
代碼示例:
import java.util.ArrayList; import java.util.Collections; import java.util.List; public class TestCollections { public static void main(String args[]) { //注意List是實現Collection接口的 List list = new ArrayList(); double array[] = { 112, 111, 23, 456, 231 }; for (int i = 0; i < array.length; i++) { list.add(new Double(array[i])); } Collections.sort(list); for (int i = 0; i < array.length; i++) { System.out.println(list.get(i)); } // 結果:23.0 111.0 112.0 231.0 456.0 } }