(二)java集合框架綜述
一集合框架圖
說明:對於以上的框架圖有如下幾點說明
1.所有集合類都位於java.util包下。Java的集合類主要由兩個接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,這兩個接口又包含了一些子接口或實現類。
2. 集合接口:6個接口(短虛線表示),表示不同集合類型,是集合框架的基礎。
3. 抽象類:5個抽象類(長虛線表示),對集合接口的部分實現。可擴展為自定義集合類。
4. 實現類:8個實現類(實線表示),對接口的具體實現。
5. Collection 接口是一組允許重復的對象。
6. Set 接口繼承 Collection,集合元素不重復。
7. List 接口繼承 Collection,允許重復,維護元素插入順序。
8. Map接口是鍵-值對象,與Collection接口沒有什麽關系。
9.Set、List和Map可以看做集合的三大類:
List集合是有序集合,集合中的元素可以重復,訪問集合中的元素可以根據元素的索引來訪問。
Set集合是無序集合,集合中的元素不可以重復,訪問集合中的元素只能根據元素本身來訪問(也是集合裏元素不允許重復的原因)。
Map集合中保存Key-value對形式的元素,訪問時只能根據每項元素的key來訪問其value。
二、總體分析
大致說明:
看上面的框架圖,先抓住它的主幹,即Collection和Map。
1、Collection是一個接口,是高度抽象出來的集合,它包含了集合的基本操作和屬性。Collection包含了List和Set兩大分支。
(1)List是一個有序的隊列,每一個元素都有它的索引。第一個元素的索引值是0。List的實現類有LinkedList, ArrayList, Vector, Stack。
(2)Set是一個不允許有重復元素的集合。Set的實現類有HastSet和TreeSet。HashSet依賴於HashMap,它實際上是通過HashMap實現的;TreeSet依賴於TreeMap,它實際上是通過TreeMap實現的。
2、Map是一個映射接口,即key-value鍵值對。Map中的每一個元素包含“一個key”和“key對應的value”。AbstractMap是個抽象類,它實現了Map接口中的大部分API。而HashMap,TreeMap,WeakHashMap都是繼承於AbstractMap。Hashtable雖然繼承於Dictionary,但它實現了Map接口。
3、接下來,再看Iterator。它是遍歷集合的工具,即我們通常通過Iterator叠代器來遍歷集合。我們說Collection依賴於Iterator,是因為Collection的實現類都要實現iterator()函數,返回一個Iterator對象。ListIterator是專門為遍歷List而存在的。
4、再看Enumeration,它是JDK 1.0引入的抽象類。作用和Iterator一樣,也是遍歷集合;但是Enumeration的功能要比Iterator少。在上面的框圖中,Enumeration只能在Hashtable, Vector, Stack中使用。
5、最後,看Arrays和Collections。它們是操作數組、集合的兩個工具類。
有了上面的整體框架之後,我們接下來對每個類分別進行分析。
三、Collection接口
Collection接口是處理對象集合的根接口,其中定義了很多對元素進行操作的方法。Collection接口有兩個主要的子接口List和Set,註意Map不是Collection的子接口,這個要牢記。
1.List接口
List集合代表一個有序集合,集合中每個元素都有其對應的順序索引。List集合允許使用重復元素,可以通過索引來訪問指定位置的集合元素。
List接口繼承於Collection接口,它可以定義一個允許重復的有序集合。因為List中的元素是有序的,所以我們可以通過使用索引(元素在List中的位置,類似於數組下標)來訪問List中的元素,這類似於Java的數組。
List接口為Collection直接接口。List所代表的是有序的Collection,即它用某種特定的插入順序來維護元素順序。用戶可以對列表中每個元素的插入位置進行精確地控制,同時可以根據元素的整數索引(在列表中的位置)訪問元素,並搜索列表中的元素。實現List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。
(1)ArrayList
ArrayList是一個動態數組,也是我們最常用的集合。它允許任何符合規則的元素插入甚至包括null。每一個ArrayList都有一個初始容量(10),該容量代表了數組的大小。隨著容器中的元素不斷增加,容器的大小也會隨著增加。在每次向容器中增加元素的同時都會進行容量檢查,當快溢出時,就會進行擴容操作。所以如果我們明確所插入元素的多少,最好指定一個初始容量值,避免過多的進行擴容操作而浪費時間、效率。
size、isEmpty、get、set、iterator 和 listIterator 操作都以固定時間運行。add 操作以分攤的固定時間運行,也就是說,添加 n 個元素需要 O(n) 時間(由於要考慮到擴容,所以這不只是添加元素會帶來分攤固定時間開銷那樣簡單)。
ArrayList擅長於隨機訪問。同時ArrayList是非同步的。
(2)LinkedList
同樣實現List接口的LinkedList與ArrayList不同,ArrayList是一個動態數組,而LinkedList是一個雙向鏈表。所以它除了有ArrayList的基本操作方法外還額外提供了get,remove,insert方法在LinkedList的首部或尾部。
由於實現的方式不同,LinkedList不能隨機訪問,它所有的操作都是要按照雙重鏈表的需要執行。在列表中索引的操作將從開頭或結尾遍歷列表(從靠近指定索引的一端)。這樣做的好處就是可以通過較低的代價在List中進行插入和刪除操作。
與ArrayList一樣,LinkedList也是非同步的。如果多個線程同時訪問一個List,則必須自己實現訪問同步。一種解決方法是在創建List時構造一個同步的List:
List list = Collections.synchronizedList(new LinkedList(...));
(3)Vector
與ArrayList相似,但是Vector是同步的。所以說Vector是線程安全的動態數組。它的操作與ArrayList幾乎一樣。
(4)Stack
Stack繼承自Vector,實現一個後進先出的堆棧。Stack提供5個額外的方法使得Vector得以被當作堆棧使用。基本的push和pop 方法,還有peek方法得到棧頂的元素,empty方法測試堆棧是否為空,search方法檢測一個元素在堆棧中的位置。Stack剛創建後是空棧。
2.Set接口
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集合中,最後只能輸出一個元素。
(1)HashSet
HashSet 是一個沒有重復元素的集合。它是由HashMap實現的,不保證元素的順序(這裏所說的沒有順序是指:元素插入的順序與輸出的順序不一致),而且HashSet允許使用null 元素。HashSet是非同步的,如果多個線程同時訪問一個哈希set,而其中至少一個線程修改了該set,那麽它必須保持外部同步。 HashSet按Hash算法來存儲集合的元素,因此具有很好的存取和查找性能。
HashSet的實現方式大致如下,通過一個HashMap存儲元素,元素是存放在HashMap的Key中,而Value統一使用一個Object對象。
HashSet使用和理解中容易出現的誤區:
a.HashSet中存放null值
HashSet中是允許存入null值的,但是在HashSet中僅僅能夠存入一個null值。
b.HashSet中存儲元素的位置是固定的
HashSet中存儲的元素的是無序的,這個沒什麽好說的,但是由於HashSet底層是基於Hash算法實現的,使用了hashcode,所以HashSet中相應的元素的位置是固定的。
c.必須小心操作可變對象(Mutable Object)。如果一個Set中的可變元素改變了自身狀態導致Object.equals(Object)=true將導致一些問題。
(2)LinkedHashSet
LinkedHashSet繼承自HashSet,其底層是基於LinkedHashMap來實現的,有序,非同步。LinkedHashSet集合同樣是根據元素的hashCode值來決定元素的存儲位置,但是它同時使用鏈表維護元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。
(3)TreeSet
TreeSet是一個有序集合,其底層是基於TreeMap實現的,非線程安全。TreeSet可以確保集合元素處於排序狀態。TreeSet支持兩種排序方式,自然排序和定制排序,其中自然排序為默認的排序方式。當我們構造TreeSet時,若使用不帶參數的構造函數,則TreeSet的使用自然比較器;若用戶需要使用自定義的比較器,則需要使用帶比較器的參數。
註意:TreeSet集合不是通過hashcode和equals函數來比較元素的.它是通過compare或者comparaeTo函數來判斷元素是否相等.compare函數通過判斷兩個對象的id,相同的id判斷為重復元素,不會被加入到集合中。
四、Map接口
Map與List、Set接口不同,它是由一系列鍵值對組成的集合,提供了key到Value的映射。同時它也沒有繼承Collection。在Map中它保證了key與value之間的一一對應關系。也就是說一個key對應一個value,所以它不能存在相同的key值,當然value值可以相同。
1.HashMap
以哈希表數據結構實現,查找對象時通過哈希函數計算其位置,它是為快速查詢而設計的,其內部定義了一個hash表數組(Entry[] table),元素會通過哈希轉換函數將元素的哈希地址轉換成數組中存放的索引,如果有沖突,則使用散列鏈表的形式將所有相同哈希地址的元素串起來,可能通過查看HashMap.Entry的源碼它是一個單鏈表結構。
2.LinkedHashMap
LinkedHashMap是HashMap的一個子類,它保留插入的順序,如果需要輸出的順序和輸入時的相同,那麽就選用LinkedHashMap。
LinkedHashMap是Map接口的哈希表和鏈接列表實現,具有可預知的叠代順序。此實現提供所有可選的映射操作,並允許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恒久不變。
LinkedHashMap實現與HashMap的不同之處在於,後者維護著一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了叠代順序,該叠代順序可以是插入順序或者是訪問順序。
根據鏈表中元素的順序可以分為:按插入順序的鏈表,和按訪問順序(調用get方法)的鏈表。默認是按插入順序排序,如果指定按訪問順序排序,那麽調用get方法後,會將這次訪問的元素移至鏈表尾部,不斷訪問可以形成按訪問順序排序的鏈表。
註意,此實現不是同步的。如果多個線程同時訪問鏈接的哈希映射,而其中至少一個線程從結構上修改了該映射,則它必須保持外部同步。
由於LinkedHashMap需要維護元素的插入順序,因此性能略低於HashMap的性能,但在叠代訪問Map裏的全部元素時將有很好的性能,因為它以鏈表來維護內部順序。
3.TreeMap
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。
五 異同點
1.ArrayList和LinkedList
(1)ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。
(2)對於隨機訪問get和set,ArrayList絕對優於LinkedList,因為LinkedList要移動指針。
(3)對於新增和刪除操作add和remove,LinedList比較占優勢,因為ArrayList要移動數據。
這一點要看實際情況的。若只對單條數據插入或刪除,ArrayList的速度反而優於LinkedList。但若是批量隨機的插入刪除數據,LinkedList的速度大大優於ArrayList. 因為ArrayList每插入一條數據,要移動插入點及之後的所有數據。
2.HashTable與HashMap
相同點:
(1)都實現了Map、Cloneable、java.io.Serializable接口。
(2)都是存儲"鍵值對(key-value)"的散列表,而且都是采用拉鏈法實現的。
不同點:
(1)歷史原因:HashTable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map接口的一個實現 。
(2)同步性:HashTable是線程安全的,也就是說是同步的,而HashMap是線程序不安全的,不是同步的 。
(3)對null值的處理:HashMap的key、value都可為null,HashTable的key、value都不可為null 。
(4)基類不同:HashMap繼承於AbstractMap,而Hashtable繼承於Dictionary。
Dictionary是一個抽象類,它直接繼承於Object類,沒有實現任何接口。Dictionary類是JDK 1.0的引入的。雖然Dictionary也支持“添加key-value鍵值對”、“獲取value”、“獲取大小”等基本操作,但它的API函數比Map少;而且Dictionary一般是通過Enumeration(枚舉類)去遍歷,Map則是通過Iterator(叠代M器)去遍歷。 然而由於Hashtable也實現了Map接口,所以,它即支持Enumeration遍歷,也支持Iterator遍歷。
AbstractMap是一個抽象類,它實現了Map接口的絕大部分API函數;為Map的具體實現類提供了極大的便利。它是JDK 1.2新增的類。
(5)支持的遍歷種類不同:HashMap只支持Iterator(叠代器)遍歷。而Hashtable支持Iterator(叠代器)和Enumeration(枚舉器)兩種方式遍歷。
3.HashMap、Hashtable、LinkedHashMap和TreeMap比較
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
4.HashSet、LinkedHashSet、TreeSet比較
Set接口
Set不允許包含相同的元素,如果試圖把兩個相同元素加入同一個集合中,add方法返回false。
Set判斷兩個對象相同不是使用==運算符,而是根據equals方法。也就是說,只要兩個對象用equals方法比較返回true,Set就不會接受這兩個對象。
HashSet
HashSet有以下特點:
-> 不能保證元素的排列順序,順序有可能發生變化。
-> 不是同步的。
-> 集合元素可以是null,但只能放入一個null。
當向HashSet結合中存入一個元素時,HashSet會調用該對象的hashCode()方法來得到該對象的hashCode值,然後根據 hashCode值來決定該對象在HashSet中存儲位置。簡單的說,HashSet集合判斷兩個元素相等的標準是兩個對象通過equals方法比較相等,並且兩個對象的hashCode()方法返回值也相等。
註意,如果要把一個對象放入HashSet中,重寫該對象對應類的equals方法,也應該重寫其hashCode()方法。其規則是如果兩個對象通過equals方法比較返回true時,其hashCode也應該相同。另外,對象中用作equals比較標準的屬性,都應該用來計算 hashCode的值。
LinkedHashSet
LinkedHashSet集合同樣是根據元素的hashCode值來決定元素的存儲位置,但是它同時使用鏈表維護元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。
LinkedHashSet在叠代訪問Set中的全部元素時,性能比HashSet好,但是插入時性能稍微遜色於HashSet。
TreeSet類
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); } }
(二)java集合框架綜述