Java 集合详解
一、集合的由來
通常,我們的程序需要根據程序運行時才知道創建多少個對象。但若非程序運行,程序開發階段,我們根本不知道到底需要多少個數量的對象,甚至不知道它的準確類型。為了滿足這些常規的編程需要,我們要求能在任何時候,任何地點創建任意數量的對象,而這些對象用什么來容納呢?我們首先想到了數組,但是數組只能放統一類型的數據,而且其長度是固定的,那怎么辦呢?集合便應運而生了!
?
為了對集合有個更加深入的了解,可以看我的這一篇文章:用 Java 數組來實現 ArrayList 集合?http://www.cnblogs.com/ysocean/p/6812674.html
?
二、集合是什么?
Java集合類存放于 java.util 包中,是一個用來存放對象的容器。
注意:①、集合只能存放對象。比如你存一個 int 型數據 1放入集合中,其實它是自動轉換成 Integer 類后存入的,Java中每一種基本類型都有對應的引用類型。
②、集合存放的是多個對象的引用,對象本身還是放在堆內存中。
③、集合可以存放不同類型,不限數量的數據類型。
?
三、Java 集合框架圖
此圖來源于:http://blog.csdn.net/u010887744/article/details/50575735
大圖可以點此訪問:https://img-blog.csdn.net/20160124221843905
?
發現一個特點,上述所有的集合類,除了 map 系列的集合,即左邊集合都實現了?Iterator?接口,這是一個用于遍歷集合中元素的接口,主要hashNext(),next(),remove()三種方法。它的一個子接口?ListIterator?在它的基礎上又添加了三種方法,分別是 add(),previous(),hasPrevious()。也就是說如果實現 Iterator 接口,那么在遍歷集合中元素的時候,只能往后遍歷,被遍歷后的元素不會再被遍歷到,通常無序集合實現的都是這個接口,比如HashSet;而那些元素有序的集合,實現的一般都是 LinkedIterator接口,實現這個接口的集合可以雙向遍歷,既可以通過next()訪問下一個元素,又可以通過previous()訪問前一個 元素,比如ArrayList。
還有一個特點就是抽象類的使用。如果要自己實現一個集合類,去實現那些抽象的接口會非常麻煩,工作量很大。這個時候就可以使用抽象類,這些抽象類中給我們提供了許多
現成的實現,我們只需要根據自己的需求重寫一些方法或者添加一些方法就可以實現自己需要的集合類,工作量大大降低。
?
四、集合詳解
①、Iterator:迭代器,它是Java集合的頂層接口(不包括 map 系列的集合,Map接口 是 map 系列集合的頂層接口)
Object next():返回迭代器剛越過的元素的引用,返回值是 Object,需要強制轉換成自己需要的類型
boolean hasNext():判斷容器內是否還有可供訪問的元素
void remove():刪除迭代器剛越過的元素
所以除了 map 系列的集合,我們都能通過迭代器來對集合中的元素進行遍歷。
注意:我們可以在源碼中追溯到集合的頂層接口,比如 Collection 接口,可以看到它繼承的是類 Iterable
那這就得說明一下 Iterator 和 Iterable 的區別:
?Iterable :存在于 java.lang 包中。
我們可以看到,里面封裝了 Iterator 接口。所以只要實現了只要實現了Iterable接口的類,就可以使用Iterator迭代器了。
?
?Iterator :存在于 java.util 包中。核心的方法next(),hasnext(),remove()。
?這里我們引用一個Iterator 的實現類 ArrayList 來看一下迭代器的使用:暫時先不管 List 集合是什么,只需要看看迭代器的用法就行了
1 //產生一個 List 集合,典型實現為 ArrayList。2 List list = new ArrayList();3 //添加三個元素4 list.add("Tom");5 list.add("Bob");6 list.add("Marry");7 //構造 List 的迭代器8 Iterator it = list.iterator();9 //通過迭代器遍歷元素 10 while(it.hasNext()){ 11 Object obj = it.next(); 12 System.out.println(obj); 13 }?
?②、Collection:List 接口和 Set 接口的父接口
看一下 Collection 集合的使用例子:
1 //我們這里將 ArrayList集合作為 Collection 的實現類2 Collection collection = new ArrayList();3 4 //添加元素5 collection.add("Tom");6 collection.add("Bob");7 8 //刪除指定元素9 collection.remove("Tom"); 10 11 //刪除所有元素 12 Collection c = new ArrayList(); 13 c.add("Bob"); 14 collection.removeAll(c); 15 16 //檢測是否存在某個元素 17 collection.contains("Tom"); 18 19 //判斷是否為空 20 collection.isEmpty(); 21 22 //利用增強for循環遍歷集合 23 for(Object obj : collection){ 24 System.out.println(obj); 25 } 26 //利用迭代器 Iterator 27 Iterator iterator = collection.iterator(); 28 while(iterator.hasNext()){ 29 Object obj = iterator.next(); 30 System.out.println(obj); 31 }?
?③、List :有序,可以重復的集合。
由于 List 接口是繼承于 Collection 接口,所以基本的方法如上所示。
1、List 接口的三個典型實現:
①、List list1 = new ArrayList();
底層數據結構是數組,查詢快,增刪慢;線程不安全,效率高
? ②、List list2 = new Vector();
底層數據結構是數組,查詢快,增刪慢;線程安全,效率低,幾乎已經淘汰了這個集合
?③、List list3 = new LinkedList();
底層數據結構是鏈表,查詢慢,增刪快;線程不安全,效率高
?
?怎么記呢?我們可以想象:
數組就像身上編了號站成一排的人,要找第10個人很容易,根據人身上的編號很快就能找到。但插入、刪除慢,要望某個位置插入或刪除一個人時,后面的人身上的編號都要變。當然,加入或刪除的人始終末尾的也快。
鏈表就像手牽著手站成一圈的人,要找第10個人不容易,必須從第一個人一個個數過去。但插入、刪除快。插入時只要解開兩個人的手,并重新牽上新加進來的人的手就可以。刪除一樣的道理。
?
?2、除此之外,List 接口遍歷還可以使用普通 for 循環進行遍歷,指定位置添加元素,替換元素等等。
1 //產生一個 List 集合,典型實現為 ArrayList2 List list = new ArrayList();3 //添加三個元素4 list.add("Tom");5 list.add("Bob");6 list.add("Marry");7 //構造 List 的迭代器8 Iterator it = list.iterator();9 //通過迭代器遍歷元素 10 while(it.hasNext()){ 11 Object obj = it.next(); 12 //System.out.println(obj); 13 } 14 15 //在指定地方添加元素 16 list.add(2, 0); 17 18 //在指定地方替換元素 19 list.set(2, 1); 20 21 //獲得指定對象的索引 22 int i=list.indexOf(1); 23 System.out.println("索引為:"+i); 24 25 //遍歷:普通for循環 26 for(int j=0;j<list.size();j++){ 27 System.out.println(list.get(j)); 28 }?
?④、Set:典型實現 HashSet()是一個無序,不可重復的集合
?1、Set hashSet = new HashSet();
①、HashSet:不能保證元素的順序;不可重復;不是線程安全的;集合元素可以為 NULL;
②、其底層其實是一個數組,存在的意義是加快查詢速度。我們知道在一般的數組中,元素在數組中的索引位置是隨機的,元素的取值和元素的位置之間不存在確定的關系,因此,在數組中查找特定的值時,需要把查找值和一系列的元素進行比較,此時的查詢效率依賴于查找過程中比較的次數。而 HashSet 集合底層數組的索引和值有一個確定的關系:index=hash(value),那么只需要調用這個公式,就能快速的找到元素或者索引。
③、對于 HashSet: 如果兩個對象通過 equals() 方法返回 true,這兩個對象的 hashCode 值也應該相同。
? 1、當向HashSet集合中存入一個元素時,HashSet會先調用該對象的hashCode()方法來得到該對象的hashCode值,然后根據hashCode值決定該對象在HashSet中的存儲位置
1.1、如果 hashCode 值不同,直接把該元素存儲到 hashCode() 指定的位置
1.2、如果 hashCode 值相同,那么會繼續判斷該元素和集合對象的 equals() 作比較
1.2.1、hashCode 相同,equals 為 true,則視為同一個對象,不保存在 hashSet()中
1.2.2、hashCode 相同,equals 為 false,則存儲在之前對象同槽位的鏈表上,這非常麻煩,我們應該約束這種情況,即保證:如果兩個對象通過 equals() 方法返回 true,這兩個對象的 hashCode 值也應該相同。
?
注意:每一個存儲到 哈希 表中的對象,都得提供 hashCode() 和 equals() 方法的實現,用來判斷是否是同一個對象
對于 HashSet 集合,我們要保證如果兩個對象通過 equals() 方法返回 true,這兩個對象的 hashCode 值也應該相同。
?
常見的 hashCode()算法:
?
?
2、Set linkedHashSet = new LinkedHashSet();
①、不可以重復,有序
因為底層采用 鏈表 和 哈希表的算法。鏈表保證元素的添加順序,哈希表保證元素的唯一性
?
?
3、Set treeSet = new TreeSet();
TreeSet:有序;不可重復,底層使用 紅黑樹算法,擅長于范圍查詢。
* ?如果使用 TreeSet() 無參數的構造器創建一個 TreeSet 對象, 則要求放入其中的元素的類必須實現 Comparable 接口所以, 在其中不能放入 null 元素
?
? ? ?* ?必須放入同樣類的對象.(默認會進行排序) 否則可能會發生類型轉換異常.我們可以使用泛型來進行限制
?
| 1 2 3 4 | Set treeSet =?new?TreeSet(); ????????treeSet.add(1);??//添加一個 Integer 類型的數據 ????????treeSet.add("a");???//添加一個 String 類型的數據 ????????System.out.println(treeSet);??//會報類型轉換異常的錯誤 |
? * 自動排序:添加自定義對象的時候,必須要實現 Comparable 接口,并要覆蓋 compareTo(Object obj) 方法來自定義比較規則
如果 this > obj,返回正數 1
如果 this < obj,返回負數 -1
如果 this = obj,返回 0 ,則認為這兩個對象相等
?
? ? ? * ?兩個對象通過 Comparable 接口 compareTo(Object obj) 方法的返回值來比較大小, 并進行升序排列
?
?
? * ?定制排序: 創建 TreeSet 對象時, 傳入 Comparator 接口的實現類.?要求: Comparator 接口的 compare 方法的返回值和 兩個元素的 equals() 方法具有一致的返回值 ?
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | public?class?TreeSetTest { ????public?static?void?main(String[] args) { ????????Person p1 =?new?Person(1); ????????Person p2 =?new?Person(2); ????????Person p3 =?new?Person(3); ????????? ????????Set<Person> set =?new?TreeSet<>(new?Person()); ????????set.add(p1); ????????set.add(p2); ????????set.add(p3); ????????System.out.println(set);??//結果為[1, 2, 3] ????} ? } ? class?Person?implements?Comparator<Person>{ ????public?int?age; ????public?Person(){} ????public?Person(int?age){ ????????this.age = age; ????} ????@Override ????/*** ?????* 根據年齡大小進行排序 ?????*/ ????public?int?compare(Person o1, Person o2) { ????????// TODO Auto-generated method stub ????????if(o1.age > o2.age){ ????????????return?1; ????????}else?if(o1.age < o2.age){ ????????????return?-1; ????????}else{ ????????????return?0; ????????} ????} ????? ????@Override ????public?String toString() { ????????// TODO Auto-generated method stub ????????return?""+this.age; ????} } |
?
? ? ?* ?當需要把一個對象放入 TreeSet 中,重寫該對象對應的 equals() 方法時,應保證該方法與 compareTo(Object obj) 方法有一致的結果
? ?
? ??
以上三個 Set 接口的實現類比較:
共同點:1、都不允許元素重復
2、都不是線程安全的類,解決辦法:Set set = Collections.synchronizedSet(set 對象)
?
?
不同點:
HashSet:不保證元素的添加順序,底層采用 哈希表算法,查詢效率高。判斷兩個元素是否相等,equals() 方法返回 true,hashCode() 值相等。即要求存入 HashSet 中的元素要覆蓋 equals() 方法和 hashCode()方法
?
LinkedHashSet:HashSet 的子類,底層采用了 哈希表算法以及 鏈表算法,既保證了元素的添加順序,也保證了查詢效率。但是整體性能要低于 HashSet
?
TreeSet:不保證元素的添加順序,但是會對集合中的元素進行排序。底層采用 紅-黑 樹算法(樹結構比較適合范圍查詢)
?
?
?
?
⑤、Map:key-value 的鍵值對,key 不允許重復,value 可以
1、嚴格來說 Map 并不是一個集合,而是兩個集合之間 的映射關系。
?2、這兩個集合沒每一條數據通過映射關系,我們可以看成是一條數據。即 Entry(key,value)。Map 可以看成是由多個 Entry 組成。
?3、因為 Map 集合即沒有實現于 Collection 接口,也沒有實現 Iterable 接口,所以不能對 Map 集合進行 for-each 遍歷。
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | Map<String,Object> hashMap =?new?HashMap<>(); ????????//添加元素到 Map 中 ????????hashMap.put("key1",?"value1"); ????????hashMap.put("key2",?"value2"); ????????hashMap.put("key3",?"value3"); ????????hashMap.put("key4",?"value4"); ????????hashMap.put("key5",?"value5"); ????????? ????????//刪除 Map 中的元素,通過 key 的值 ????????hashMap.remove("key1"); ????????? ????????//通過 get(key) 得到 Map 中的value ????????Object str1 = hashMap.get("key1"); ????????? ????????//可以通過 添加 方法來修改 Map 中的元素 ????????hashMap.put("key2",?"修改 key2 的 Value"); ????????? ????????//通過 map.values() 方法得到 Map 中的 value 集合 ????????Collection<Object> value = hashMap.values(); ????????for(Object obj : value){ ????????????//System.out.println(obj); ????????} ????????? ????????//通過 map.keySet() 得到 Map 的key 的集合,然后 通過 get(key) 得到 Value ????????Set<String> set = hashMap.keySet(); ????????for(String str : set){ ????????????Object obj = hashMap.get(str); ????????????//System.out.println(str+"="+obj); ????????} ????????? ????????//通過 Map.entrySet() 得到 Map 的 Entry集合,然后遍歷 ????????Set<Map.Entry<String, Object>> entrys = hashMap.entrySet(); ????????for(Map.Entry<String, Object> entry: entrys){ ????????????String key = entry.getKey(); ????????????Object value2 = entry.getValue(); ????????????System.out.println(key+"="+value2); ????????} ????????? ????????System.out.println(hashMap); |
Map 的常用實現類:
?
?
?
?
?
⑥、Map 和 Set 集合的關系
1、都有幾個類型的集合。HashMap 和 HashSet ,都采 哈希表算法;TreeMap 和 TreeSet 都采用 紅-黑樹算法;LinkedHashMap 和 LinkedHashSet 都采用 哈希表算法和紅-黑樹算法。
2、分析 Set 的底層源碼,我們可以看到,Set 集合 就是 由 Map 集合的 Key 組成。
?
作者:YSOcean
出處:http://www.cnblogs.com/ysocean/
總結
- 上一篇: Tomcat 部署项目的三种方法
- 下一篇: Java数据结构和算法(一)——简介