本文主要關注Java編程中涉及到的各類集合類,以及它們的使用場景html
相關學習資料java
http://files.cnblogs.com/LittleHann/java%E9%9B%86%E5%90%88%E6%8E%92%E5%BA%8F%E5%8F%8Ajava%E9%9B%86%E5%90%88%E7%B1%BB%E8%AF%A6%E8%A7%A3%28collection%E3%80%81list%E3%80%81map%E3%80%81set%29.rar http://blog.sina.com.cn/s/blog_a345a8960101k9vx.html http://f51889920.iteye.com/blog/1884810
目錄算法
1. Java集合類基本概念 2. Java集合類架構層次關係 3. Java集合類的應用場景代碼
1. Java集合類基本概念數據庫
在編程中,經常須要集中存放多個數據。從傳統意義上講,數組是咱們的一個很好的選擇,前提是咱們事先已經明確知道咱們將要保存的對象的數量。一旦在數組初始化時指定了這個數組長度,這個數組長度就是不可變的,若是咱們須要保存一個能夠動態增加的數據(在編譯時沒法肯定具體的數量),java的集合類就是一個很好的設計方案了。編程
集合類主要負責保存、盛裝其餘數據,所以集合類也被稱爲容器類。因此的集合類都位於java.util包下,後來爲了處理多線程環境下的併發安全問題,java5還在java.util.concurrent包下提供了一些多線程支持的集合類。windows
在學習Java中的集合類的API、編程原理的時候,咱們必定要明白,"集合"是一個很古老的數學概念,它遠遠早於Java的出現。從數學概念的角度來理解集合能幫助咱們更好的理解編程中何時該使用什麼類型的集合類。數組
Java容器類類庫的用途是"保存對象",並將其劃分爲兩個不一樣的概念:緩存
1) Collection 一組"對立"的元素,一般這些元素都服從某種規則 1.1) List必須保持元素特定的順序 1.2) Set不能有重複元素 1.3) Queue保持一個隊列(先進先出)的順序 2) Map 一組成對的"鍵值對"對象
Collection和Map的區別在於容器中每一個位置保存的元素個數:安全
1) Collection 每一個位置只能保存一個元素(對象) 2) Map保存的是"鍵值對",就像一個小型數據庫。咱們能夠經過"鍵"找到該鍵對應的"值"
2. Java集合類架構層次關係數據結構
1. Interface Iterable 迭代器接口,這是Collection類的父接口。實現這個Iterable接口的對象容許使用foreach進行遍歷,也就是說,全部的Collection集合對象都具備"foreach可遍歷性"。這個Iterable接口只
有一個方法: iterator()。它返回一個表明當前集合對象的泛型<T>迭代器,用於以後的遍歷操做 1.1 Collection Collection是最基本的集合接口,一個Collection表明一組Object的集合,這些Object被稱做Collection的元素。Collection是一個接口,用以提供規範定義,不能被實例化使用 1) Set Set集合相似於一個罐子,"丟進"Set集合裏的多個對象之間沒有明顯的順序。Set繼承自Collection接口,不能包含有重複元素(記住,這是整個Set類層次的共有屬性)。 Set判斷兩個對象相同不是使用"=="運算符,而是根據equals方法。也就是說,咱們在加入一個新元素的時候,若是這個新元素對象和Set中已有對象進行注意equals比較都返回false,
則Set就會接受這個新元素對象,不然拒絕。 由於Set的這個制約,在使用Set集合的時候,應該注意兩點:1) 爲Set集合裏的元素的實現類實現一個有效的equals(Object)方法、2) 對Set的構造函數,傳入的Collection參數不能包
含重複的元素 1.1) HashSet HashSet是Set接口的典型實現,HashSet使用HASH算法來存儲集合中的元素,所以具備良好的存取和查找性能。當向HashSet集合中存入一個元素時,HashSet會調用該對象的
hashCode()方法來獲得該對象的hashCode值,而後根據該HashCode值決定該對象在HashSet中的存儲位置。 值得主要的是,HashSet集合判斷兩個元素相等的標準是兩個對象經過equals()方法比較相等,而且兩個對象的hashCode()方法的返回值相等 1.1.1) LinkedHashSet LinkedHashSet集合也是根據元素的hashCode值來決定元素的存儲位置,但和HashSet不一樣的是,它同時使用鏈表維護元素的次序,這樣使得元素看起來是以插入的順序保存的。
當遍歷LinkedHashSet集合裏的元素時,LinkedHashSet將會按元素的添加順序來訪問集合裏的元素。 LinkedHashSet須要維護元素的插入順序,所以性能略低於HashSet的性能,但在迭代訪問Set裏的所有元素時(遍歷)將有很好的性能(鏈表很適合進行遍歷) 1.2) SortedSet 此接口主要用於排序操做,即實現此接口的子類都屬於排序的子類 1.2.1) TreeSet TreeSet是SortedSet接口的實現類,TreeSet能夠確保集合元素處於排序狀態 1.3) EnumSet EnumSet是一個專門爲枚舉類設計的集合類,EnumSet中全部元素都必須是指定枚舉類型的枚舉值,該枚舉類型在建立EnumSet時顯式、或隱式地指定。EnumSet的集合元素也是有序的,
它們以枚舉值在Enum類內的定義順序來決定集合元素的順序 2) List List集合表明一個元素有序、可重複的集合,集合中每一個元素都有其對應的順序索引。List集合容許加入重複元素,由於它能夠經過索引來訪問指定位置的集合元素。List集合默認按元素
的添加順序設置元素的索引 2.1) ArrayList ArrayList是基於數組實現的List類,它封裝了一個動態的增加的、容許再分配的Object[]數組。 2.2) Vector Vector和ArrayList在用法上幾乎徹底相同,但因爲Vector是一個古老的集合,因此Vector提供了一些方法名很長的方法,但隨着JDK1.2之後,java提供了系統的集合框架,就將
Vector改成實現List接口,統一納入集合框架體系中 2.2.1) Stack Stack是Vector提供的一個子類,用於模擬"棧"這種數據結構(LIFO後進先出) 2.3) LinkedList implements List<E>, Deque<E>。實現List接口,能對它進行隊列操做,便可以根據索引來隨機訪問集合中的元素。同時它還實現Deque接口,即能將LinkedList看成雙端隊列
使用。天然也能夠被看成"棧來使用" 3) Queue Queue用於模擬"隊列"這種數據結構(先進先出 FIFO)。隊列的頭部保存着隊列中存放時間最長的元素,隊列的尾部保存着隊列中存放時間最短的元素。新元素插入(offer)到隊列的尾部,
訪問元素(poll)操做會返回隊列頭部的元素,隊列不容許隨機訪問隊列中的元素。結合生活中常見的排隊就會很好理解這個概念 3.1) PriorityQueue PriorityQueue並非一個比較標準的隊列實現,PriorityQueue保存隊列元素的順序並非按照加入隊列的順序,而是按照隊列元素的大小進行從新排序,這點從它的類名也能夠
看出來 3.2) Deque Deque接口表明一個"雙端隊列",雙端隊列能夠同時從兩端來添加、刪除元素,所以Deque的實現類既能夠當成隊列使用、也能夠當成棧使用 3.2.1) ArrayDeque 是一個基於數組的雙端隊列,和ArrayList相似,它們的底層都採用一個動態的、可重分配的Object[]數組來存儲集合元素,當集合元素超出該數組的容量時,系統會在底層重
新分配一個Object[]數組來存儲集合元素 3.2.2) LinkedList 1.2 Map Map用於保存具備"映射關係"的數據,所以Map集合裏保存着兩組值,一組值用於保存Map裏的key,另一組值用於保存Map裏的value。key和value均可以是任何引用類型的數據。Map的key不允
許重複,即同一個Map對象的任何兩個key經過equals方法比較結果老是返回false。 關於Map,咱們要從代碼複用的角度去理解,java是先實現了Map,而後經過包裝了一個全部value都爲null的Map就實現了Set集合 Map的這些實現類和子接口中key集的存儲形式和Set集合徹底相同(即key不能重複) Map的這些實現類和子接口中value集的存儲形式和List很是相似(即value能夠重複、根據索引來查找) 1) HashMap 和HashSet集合不能保證元素的順序同樣,HashMap也不能保證key-value對的順序。而且相似於HashSet判斷兩個key是否相等的標準也是: 兩個key經過equals()方法比較返回true、
同時兩個key的hashCode值也必須相等 1.1) LinkedHashMap LinkedHashMap也使用雙向鏈表來維護key-value對的次序,該鏈表負責維護Map的迭代順序,與key-value對的插入順序一致(注意和TreeMap對全部的key-value進行排序進行區
分) 2) Hashtable 是一個古老的Map實現類 2.1) Properties Properties對象在處理屬性文件時特別方便(windows平臺上的.ini文件),Properties類能夠把Map對象和屬性文件關聯起來,從而能夠把Map對象中的key-value對寫入到屬性文
件中,也能夠把屬性文件中的"屬性名-屬性值"加載到Map對象中 3) SortedMap 正如Set接口派生出SortedSet子接口,SortedSet接口有一個TreeSet實現類同樣,Map接口也派生出一個SortedMap子接口,SortedMap接口也有一個TreeMap實現類 3.1) TreeMap TreeMap就是一個紅黑樹數據結構,每一個key-value對即做爲紅黑樹的一個節點。TreeMap存儲key-value對(節點)時,須要根據key對節點進行排序。TreeMap能夠保證全部的
key-value對處於有序狀態。一樣,TreeMap也有兩種排序方式: 天然排序、定製排序 4) WeakHashMap WeakHashMap與HashMap的用法基本類似。區別在於,HashMap的key保留了對實際對象的"強引用",這意味着只要該HashMap對象不被銷燬,該HashMap所引用的對象就不會被垃圾回收。
但WeakHashMap的key只保留了對實際對象的弱引用,這意味着若是WeakHashMap對象的key所引用的對象沒有被其餘強引用變量所引用,則這些key所引用的對象可能被垃圾回收,當垃
圾回收了該key所對應的實際對象以後,WeakHashMap也可能自動刪除這些key所對應的key-value對 5) IdentityHashMap IdentityHashMap的實現機制與HashMap基本類似,在IdentityHashMap中,當且僅當兩個key嚴格相等(key1 == key2)時,IdentityHashMap才認爲兩個key相等 6) EnumMap EnumMap是一個與枚舉類一塊兒使用的Map實現,EnumMap中的全部key都必須是單個枚舉類的枚舉值。建立EnumMap時必須顯式或隱式指定它對應的枚舉類。EnumMap根據key的天然順序
(即枚舉值在枚舉類中的定義順序)
3. Java集合類的應用場景代碼
學習了集合類的基本架構框架以後,咱們接着來學習它們各自的應用場景、以及細節處的注意事項
0x1: Set
HashSet
import java.util.*; //類A的equals方法老是返回true,但沒有重寫其hashCode()方法。不能保證當前對象是HashSet中的惟一對象 class A { public boolean equals(Object obj) { return true; } } //類B的hashCode()方法老是返回1,但沒有重寫其equals()方法。不能保證當前對象是HashSet中的惟一對象 class B { public int hashCode() { return 1; } } //類C的hashCode()方法老是返回2,且有重寫其equals()方法 class C { public int hashCode() { return 2; } public boolean equals(Object obj) { return true; } } public class HashSetTest { public static void main(String[] args) { HashSet books = new HashSet(); //分別向books集合中添加兩個A對象,兩個B對象,兩個C對象 books.add(new A()); books.add(new A()); books.add(new B()); books.add(new B()); books.add(new C()); books.add(new C()); System.out.println(books); } }
result:
[B@1, B@1, C@2, A@3bc257, A@785d65]
能夠看到,若是兩個對象經過equals()方法比較返回true,但這兩個對象的hashCode()方法返回不一樣的hashCode值時,這將致使HashSet會把這兩個對象保存在Hash表的不一樣位置,從而使對象能夠添加成功,這就與Set集合的規則有些出入了。因此,咱們要明確的是: equals()決定是否能夠加入HashSet、而hashCode()決定存放的位置,它們二者必須同時知足才能容許一個新元素加入HashSet
可是要注意的是: 若是兩個對象的hashCode相同,可是它們的equlas返回值不一樣,HashSet會在這個位置用鏈式結構來保存多個對象。而HashSet訪問集合元素時也是根據元素的HashCode值來快速定位的,這種鏈式結構會致使性能降低。
因此若是須要把某個類的對象保存到HashSet集合中,咱們在重寫這個類的equlas()方法和hashCode()方法時,應該儘可能保證兩個對象經過equals()方法比較返回true時,它們的hashCode()方法返回值也相等
LinkedHashSet
import java.util.*; public class LinkedHashSetTest { public static void main(String[] args) { LinkedHashSet books = new LinkedHashSet(); books.add("Java"); books.add("LittleHann"); System.out.println(books); //刪除 Java books.remove("Java"); //從新添加 Java books.add("Java"); System.out.println(books); } }
元素的順序老是與添加順序一致,同時要明白的是,LinkedHashSetTest是HashSet的子類,所以它不容許集合元素重複
TreeSet
import java.util.*; public class TreeSetTest { public static void main(String[] args) { TreeSet nums = new TreeSet(); //向TreeSet中添加四個Integer對象 nums.add(5); nums.add(2); nums.add(10); nums.add(-9); //輸出集合元素,看到集合元素已經處於排序狀態 System.out.println(nums); //輸出集合裏的第一個元素 System.out.println(nums.first()); //輸出集合裏的最後一個元素 System.out.println(nums.last()); //返回小於4的子集,不包含4 System.out.println(nums.headSet(4)); //返回大於5的子集,若是Set中包含5,子集中還包含5 System.out.println(nums.tailSet(5)); //返回大於等於-3,小於4的子集。 System.out.println(nums.subSet(-3 , 4)); } }
與HashSet集合採用hash算法來決定元素的存儲位置不一樣,TreeSet採用紅黑樹的數據結構來存儲集合元素。TreeSet支持兩種排序方式: 天然排序、定製排序
1. 天然排序:
TreeSet會調用集合元素的compareTo(Object obj)方法來比較元素之間的大小關係,而後將集合元素按升序排序,即天然排序。若是試圖把一個對象添加到TreeSet時,則該對象的類必須實現Comparable接口,不然程序會拋出異常。
當把一個對象加入TreeSet集合中時,TreeSet會調用該對象的compareTo(Object obj)方法與容器中的其餘對象比較大小,而後根據紅黑樹結構找到它的存儲位置。若是兩個對象經過compareTo(Object obj)方法比較相等,新對象將沒法添加到TreeSet集合中(牢記Set是不容許重複的概念)。
注意: 當須要把一個對象放入TreeSet中,重寫該對象對應類的equals()方法時,應該保證該方法與compareTo(Object obj)方法有一致的結果,即若是兩個對象經過equals()方法比較返回true時,這兩個對象經過compareTo(Object obj)方法比較結果應該也爲0(即相等)
看到這裏,咱們應該明白:
1) 對與Set來講,它定義了equals()爲惟一性判斷的標準,而對於到了具體的實現,HashSet、TreeSet來講,它們又會有本身特有的惟一性判斷標準,只有同時知足了才能斷定爲惟一性 2) 咱們在操做這些集合類的時候,對和惟一性判斷有關的函數重寫要重點關注
2. 定製排序
TreeSet的天然排序是根據集合元素的大小,TreeSet將它們以升序排序。若是咱們須要實現定製排序,則能夠經過Comparator接口的幫助(相似PHP中的array_map回調處理函數的思想)。該接口裏包含一個int compare(T o1, T o2)方法,該方法用於比較大小
import java.util.*; class M { int age; public M(int age) { this.age = age; } public String toString() { return "M[age:" + age + "]"; } } public class TreeSetTest4 { public static void main(String[] args) { TreeSet ts = new TreeSet(new Comparator() { //根據M對象的age屬性來決定大小 public int compare(Object o1, Object o2) { M m1 = (M)o1; M m2 = (M)o2; return m1.age > m2.age ? -1 : m1.age < m2.age ? 1 : 0; } }); ts.add(new M(5)); ts.add(new M(-3)); ts.add(new M(9)); System.out.println(ts); } }
看到這裏,咱們須要梳理一下關於排序的概念
1) equals、compareTo決定的是怎麼比的問題,即用什麼field進行大小比較 2) 天然排序、定製排序、Comparator決定的是誰大的問題,即按什麼順序(升序、降序)進行排序 它們的關注點是不一樣的,必定要注意區分
EnumSet
import java.util.*; enum Season { SPRING,SUMMER,FALL,WINTER } public class EnumSetTest { public static void main(String[] args) { //建立一個EnumSet集合,集合元素就是Season枚舉類的所有枚舉值 EnumSet es1 = EnumSet.allOf(Season.class); //輸出[SPRING,SUMMER,FALL,WINTER] System.out.println(es1); //建立一個EnumSet空集合,指定其集合元素是Season類的枚舉值。 EnumSet es2 = EnumSet.noneOf(Season.class); //輸出[] System.out.println(es2); //手動添加兩個元素 es2.add(Season.WINTER); es2.add(Season.SPRING); //輸出[SPRING,WINTER] System.out.println(es2); //以指定枚舉值建立EnumSet集合 EnumSet es3 = EnumSet.of(Season.SUMMER , Season.WINTER); //輸出[SUMMER,WINTER] System.out.println(es3); EnumSet es4 = EnumSet.range(Season.SUMMER , Season.WINTER); //輸出[SUMMER,FALL,WINTER] System.out.println(es4); //新建立的EnumSet集合的元素和es4集合的元素有相同類型, //es5的集合元素 + es4集合元素 = Season枚舉類的所有枚舉值 EnumSet es5 = EnumSet.complementOf(es4); //輸出[SPRING] System.out.println(es5); } }
以上就是Set集合類的編程應用場景。那麼應該怎樣選擇什麼時候使用這些集合類呢?
1) HashSet的性能老是比TreeSet好(特別是最經常使用的添加、查詢元素等操做),由於TreeSet須要額外的紅黑樹算法來維護集合元素的次序。只有當須要一個保持排序的Set時,才應該使用TreeSet,不然都應該使用HashSet 2) 對於普通的插入、刪除操做,LinkedHashSet比HashSet要略慢一點,這是由維護鏈表所帶來的開銷形成的。不過,由於有了鏈表的存在,遍歷LinkedHashSet會更快 3) EnumSet是全部Set實現類中性能最好的,但它只能保存同一個枚舉類的枚舉值做爲集合元素 4) HashSet、TreeSet、EnumSet都是"線程不安全"的,一般能夠經過Collections工具類的synchronizedSortedSet方法來"包裝"該Set集合。 SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));
0x2: List
ArrayList
若是一開始就知道ArrayList集合須要保存多少元素,則能夠在建立它們時就指定initialCapacity大小,這樣能夠減小從新分配的次數,提供性能,ArrayList還提供了以下方法來從新分配Object[]數組
1) ensureCapacity(int minCapacity): 將ArrayList集合的Object[]數組長度增長minCapacity 2) trimToSize(): 調整ArrayList集合的Object[]數組長度爲當前元素的個數。程序能夠經過此方法來減小ArrayList集合對象佔用的內存空間
import java.util.*; public class ListTest { public static void main(String[] args) { List books = new ArrayList(); //向books集合中添加三個元素 books.add(new String("輕量級Java EE企業應用實戰")); books.add(new String("瘋狂Java講義")); books.add(new String("瘋狂Android講義")); System.out.println(books); //將新字符串對象插入在第二個位置 books.add(1 , new String("瘋狂Ajax講義")); for (int i = 0 ; i < books.size() ; i++ ) { System.out.println(books.get(i)); } //刪除第三個元素 books.remove(2); System.out.println(books); //判斷指定元素在List集合中位置:輸出1,代表位於第二位 System.out.println(books.indexOf(new String("瘋狂Ajax講義"))); //① //將第二個元素替換成新的字符串對象 books.set(1, new String("LittleHann")); System.out.println(books); //將books集合的第二個元素(包括) //到第三個元素(不包括)截取成子集合 System.out.println(books.subList(1 , 2)); }
Stack
注意Stack的後進先出的特色
import java.util.*; public class VectorTest { public static void main(String[] args) { Stack v = new Stack(); //依次將三個元素push入"棧" v.push("瘋狂Java講義"); v.push("輕量級Java EE企業應用實戰"); v.push("瘋狂Android講義"); //輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰 , 瘋狂Android講義] System.out.println(v); //訪問第一個元素,但並不將其pop出"棧",輸出:瘋狂Android講義 System.out.println(v.peek()); //依然輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰 , 瘋狂Android講義] System.out.println(v); //pop出第一個元素,輸出:瘋狂Android講義 System.out.println(v.pop()); //輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰] System.out.println(v); } }
LinkedList
import java.util.*; public class LinkedListTest { public static void main(String[] args) { LinkedList books = new LinkedList(); //將字符串元素加入隊列的尾部(雙端隊列) books.offer("瘋狂Java講義"); //將一個字符串元素加入棧的頂部(雙端隊列) books.push("輕量級Java EE企業應用實戰"); //將字符串元素添加到隊列的頭(至關於棧的頂部) books.offerFirst("瘋狂Android講義"); for (int i = 0; i < books.size() ; i++ ) { System.out.println(books.get(i)); } //訪問、並不刪除棧頂的元素 System.out.println(books.peekFirst()); //訪問、並不刪除隊列的最後一個元素 System.out.println(books.peekLast()); //將棧頂的元素彈出"棧" System.out.println(books.pop()); //下面輸出將看到隊列中第一個元素被刪除 System.out.println(books); //訪問、並刪除隊列的最後一個元素 System.out.println(books.pollLast()); //下面輸出將看到隊列中只剩下中間一個元素: //輕量級Java EE企業應用實戰 System.out.println(books); } }
從代碼中咱們能夠看到,LinkedList同時表現出了雙端隊列、棧的用法。功能很是強大
0x3: Queue
PriorityQueue
import java.util.*; public class PriorityQueueTest { public static void main(String[] args) { PriorityQueue pq = new PriorityQueue(); //下面代碼依次向pq中加入四個元素 pq.offer(6); pq.offer(-3); pq.offer(9); pq.offer(0); //輸出pq隊列,並非按元素的加入順序排列, //而是按元素的大小順序排列,輸出[-3, 0, 9, 6] System.out.println(pq); //訪問隊列第一個元素,其實就是隊列中最小的元素:-3 System.out.println(pq.poll()); } }
PriorityQueue不容許插入null元素,它還須要對隊列元素進行排序,PriorityQueue的元素有兩種排序方式
1) 天然排序: 採用天然順序的PriorityQueue集合中的元素對象都必須實現了Comparable接口,並且應該是同一個類的多個實例,不然可能致使ClassCastException異常 2) 定製排序 建立PriorityQueue隊列時,傳入一個Comparator對象,該對象負責對隊列中的全部元素進行排序 關於天然排序、定製排序的原理和以前說的TreeSet相似
ArrayDeque
import java.util.*; public class ArrayDequeTest { public static void main(String[] args) { ArrayDeque stack = new ArrayDeque(); //依次將三個元素push入"棧" stack.push("瘋狂Java講義"); stack.push("輕量級Java EE企業應用實戰"); stack.push("瘋狂Android講義"); //輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰 , 瘋狂Android講義] System.out.println(stack); //訪問第一個元素,但並不將其pop出"棧",輸出:瘋狂Android講義 System.out.println(stack.peek()); //依然輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰 , 瘋狂Android講義] System.out.println(stack); //pop出第一個元素,輸出:瘋狂Android講義 System.out.println(stack.pop()); //輸出:[瘋狂Java講義, 輕量級Java EE企業應用實戰] System.out.println(stack); } }
以上就是List集合類的編程應用場景。咱們來梳理一下思路
1. java提供的List就是一個"線性表接口",ArrayList(基於數組的線性表)、LinkedList(基於鏈的線性表)是線性表的兩種典型實現 2. Queue表明了隊列,Deque表明了雙端隊列(既能夠做爲隊列使用、也能夠做爲棧使用) 3. 由於數組以一塊連續內存來保存全部的數組元素,因此數組在隨機訪問時性能最好。因此的內部以數組做爲底層實現的集合在隨機訪問時性能最好。 4. 內部以鏈表做爲底層實現的集合在執行插入、刪除操做時有很好的性能 5. 進行迭代操做時,以鏈表做爲底層實現的集合比以數組做爲底層實現的集合性能好
咱們以前說過,Collection接口繼承了Iterable接口,也就是說,咱們以上學習到的全部的Collection集合類都具備"可遍歷性"
Iterable接口也是java集合框架的成員,它隱藏了各類Collection實現類的底層細節,嚮應用程序提供了遍歷Collection集合元素的統一編程接口:
1) boolean hasNext(): 是否還有下一個未遍歷過的元素 2) Object next(): 返回集合裏的下一個元素 3) void remove(): 刪除集合裏上一次next方法返回的元素
iterator實現遍歷:
import java.util.*; public class IteratorTest { public static void main(String[] args) { //建立一個集合 Collection books = new HashSet(); books.add("輕量級Java EE企業應用實戰"); books.add("瘋狂Java講義"); books.add("瘋狂Android講義"); //獲取books集合對應的迭代器 Iterator it = books.iterator(); while(it.hasNext()) { //it.next()方法返回的數據類型是Object類型, //須要強制類型轉換 String book = (String)it.next(); System.out.println(book); if (book.equals("瘋狂Java講義")) { //從集合中刪除上一次next方法返回的元素 it.remove(); } //對book變量賦值,不會改變集合元素自己 book = "測試字符串"; } System.out.println(books); } }
從代碼能夠看出,iterator必須依附於Collection對象,如有一個iterator對象,必然有一個與之關聯的Collection對象。
除了可使用iterator接口迭代訪問Collection集合裏的元素以外,使用java5提供的foreach循環迭代訪問集合元素更加便捷
foreach實現遍歷:
import java.util.*; public class ForeachTest { public static void main(String[] args) { //建立一個集合 Collection books = new HashSet(); books.add(new String("輕量級Java EE企業應用實戰")); books.add(new String("瘋狂Java講義")); books.add(new String("瘋狂Android講義")); for (Object obj : books) { //此處的book變量也不是集合元素自己 String book = (String)obj; System.out.println(book); if (book.equals("瘋狂Android講義")) { //下面代碼會引起ConcurrentModificationException異常 //books.remove(book); } } System.out.println(books); } }
除了Collection固有的iterator()方法,List還額外提供了一個listIterator()方法,該方法返回一個ListIterator對象,ListIterator接口繼承了Iterator接口,提供了專門操做List的方法。ListIterator接口在Iterator接口的繼承上增長了以下方法:
1) boolean hasPrevious(): 返回該迭代器關聯的集合是否還有上一個元素 2) Object previous(): 返回該迭代器的上一個元素(向前迭代) 3) void add(): 在指定位置插入一個元素
ListIterator實現遍歷:
import java.util.*; public class ListIteratorTest { public static void main(String[] args) { String[] books = { "瘋狂Java講義", "輕量級Java EE企業應用實戰" }; List bookList = new ArrayList(); for (int i = 0; i < books.length ; i++ ) { bookList.add(books[i]); } ListIterator lit = bookList.listIterator(); while (lit.hasNext()) { System.out.println(lit.next()); lit.add("-------分隔符-------"); } System.out.println("=======下面開始反向迭代======="); while(lit.hasPrevious()) { System.out.println(lit.previous()); } } }
0x4: Map
HashMap、Hashtable
import java.util.*; class A { int count; public A(int count) { this.count = count; } //根據count的值來判斷兩個對象是否相等。 public boolean equals(Object obj) { if (obj == this) return true; if (obj!=null && obj.getClass()==A.class) { A a = (A)obj; return this.count == a.count; } return false; } //根據count來計算hashCode值。 public int hashCode() { return this.count; } } class B { //重寫equals()方法,B對象與任何對象經過equals()方法比較都相等 public boolean equals(Object obj) { return true; } } public class HashtableTest { public static void main(String[] args) { Hashtable ht = new Hashtable(); ht.put(new A(60000) , "瘋狂Java講義"); ht.put(new A(87563) , "輕量級Java EE企業應用實戰"); ht.put(new A(1232) , new B()); System.out.println(ht); //只要兩個對象經過equals比較返回true, //Hashtable就認爲它們是相等的value。 //因爲Hashtable中有一個B對象, //它與任何對象經過equals比較都相等,因此下面輸出true。 System.out.println(ht.containsValue("測試字符串")); //① //只要兩個A對象的count相等,它們經過equals比較返回true,且hashCode相等 //Hashtable即認爲它們是相同的key,因此下面輸出true。 System.out.println(ht.containsKey(new A(87563))); //② //下面語句能夠刪除最後一個key-value對 ht.remove(new A(1232)); //③ //經過返回Hashtable的全部key組成的Set集合, //從而遍歷Hashtable每一個key-value對 for (Object key : ht.keySet()) { System.out.print(key + "---->"); System.out.print(ht.get(key) + "\n"); } } }
當使用自定義類做爲HashMap、Hashtable的key時,若是重寫該類的equals(Object obj)和hashCode()方法,則應該保證兩個方法的判斷標準一致--當兩個key經過equals()方法比較返回true時,兩個key的hashCode()的返回值也應該相同
LinkedHashMap
import java.util.*; public class LinkedHashMapTest { public static void main(String[] args) { LinkedHashMap scores = new LinkedHashMap(); scores.put("語文" , 80); scores.put("英文" , 82); scores.put("數學" , 76); //遍歷scores裏的全部的key-value對 for (Object key : scores.keySet()) { System.out.println(key + "------>" + scores.get(key)); } } }
Properties
import java.util.*; import java.io.*; public class PropertiesTest { public static void main(String[] args) throws Exception { Properties props = new Properties(); //向Properties中增長屬性 props.setProperty("username" , "yeeku"); props.setProperty("password" , "123456"); //將Properties中的key-value對保存到a.ini文件中 props.store(new FileOutputStream("a.ini"), "comment line"); //① //新建一個Properties對象 Properties props2 = new Properties(); //向Properties中增長屬性 props2.setProperty("gender" , "male"); //將a.ini文件中的key-value對追加到props2中 props2.load(new FileInputStream("a.ini") ); //② System.out.println(props2); } }
Properties還能夠把key-value對以XML文件的形式保存起來,也能夠從XML文件中加載key-value對
TreeMap
import java.util.*; class R implements Comparable { int count; public R(int count) { this.count = count; } public String toString() { return "R[count:" + count + "]"; } //根據count來判斷兩個對象是否相等。 public boolean equals(Object obj) { if (this == obj) return true; if (obj!=null && obj.getClass()==R.class) { R r = (R)obj; return r.count == this.count; } return false; } //根據count屬性值來判斷兩個對象的大小。 public int compareTo(Object obj) { R r = (R)obj; return count > r.count ? 1 : count < r.count ? -1 : 0; } } public class TreeMapTest { public static void main(String[] args) { TreeMap tm = new TreeMap(); tm.put(new R(3) , "輕量級Java EE企業應用實戰"); tm.put(new R(-5) , "瘋狂Java講義"); tm.put(new R(9) , "瘋狂Android講義"); System.out.println(tm); //返回該TreeMap的第一個Entry對象 System.out.println(tm.firstEntry()); //返回該TreeMap的最後一個key值 System.out.println(tm.lastKey()); //返回該TreeMap的比new R(2)大的最小key值。 System.out.println(tm.higherKey(new R(2))); //返回該TreeMap的比new R(2)小的最大的key-value對。 System.out.println(tm.lowerEntry(new R(2))); //返回該TreeMap的子TreeMap System.out.println(tm.subMap(new R(-1) , new R(4))); } }
從代碼中能夠看出,相似於TreeSet中判斷兩個元素是否相等的標準,TreeMap中判斷兩個key相等的標準是:
1) 兩個key經過compareTo()方法返回0 2) equals()放回true
咱們在重寫這兩個方法的時候必定要保證它們的邏輯關係一致。
再次強調一下:
Set和Map的關係十分密切,java源碼就是先實現了HashMap、TreeMap等集合,而後經過包裝一個全部的value都爲null的Map集合實現了Set集合類
WeakHashMap
import java.util.*; public class WeakHashMapTest { public static void main(String[] args) { WeakHashMap whm = new WeakHashMap(); //將WeakHashMap中添加三個key-value對, //三個key都是匿名字符串對象(沒有其餘引用) whm.put(new String("語文") , new String("良好")); whm.put(new String("數學") , new String("及格")); whm.put(new String("英文") , new String("中等")); //將WeakHashMap中添加一個key-value對, //該key是一個系統緩存的字符串對象。"java"是一個常量字符串強引用 whm.put("java" , new String("中等")); //輸出whm對象,將看到4個key-value對。 System.out.println(whm); //通知系統當即進行垃圾回收 System.gc(); System.runFinalization(); //一般狀況下,將只看到一個key-value對。 System.out.println(whm); } }
若是須要使用WeakHashMap的key來保留對象的弱引用,則不要讓key所引用的對象具備任何強引用,不然將失去使用WeakHashMap的意義
IdentityHashMap
import java.util.*; public class IdentityHashMapTest { public static void main(String[] args) { IdentityHashMap ihm = new IdentityHashMap(); //下面兩行代碼將會向IdentityHashMap對象中添加兩個key-value對 ihm.put(new String("語文") , 89); ihm.put(new String("語文") , 78); //下面兩行代碼只會向IdentityHashMap對象中添加一個key-value對 ihm.put("java" , 93); ihm.put("java" , 98); System.out.println(ihm); } }
EnumMap
import java.util.*; enum Season { SPRING,SUMMER,FALL,WINTER } public class EnumMapTest { public static void main(String[] args) { //建立一個EnumMap對象,該EnumMap的全部key //必須是Season枚舉類的枚舉值 EnumMap enumMap = new EnumMap(Season.class); enumMap.put(Season.SUMMER , "夏日炎炎"); enumMap.put(Season.SPRING , "春暖花開"); System.out.println(enumMap); } }
與建立普通Map有所區別的是,建立EnumMap是必須指定一個枚舉類,從而將該EnumMap和指定枚舉類關聯起來
以上就是Map集合類的編程應用場景。咱們來梳理一下思路
1) HashMap和Hashtable的效率大體相同,由於它們的實現機制幾乎徹底同樣。但HashMap一般比Hashtable要快一點,由於Hashtable須要額外的線程同步控制 2) TreeMap一般比HashMap、Hashtable要慢(尤爲是在插入、刪除key-value對時更慢),由於TreeMap底層採用紅黑樹來管理key-value對 3) 使用TreeMap的一個好處就是: TreeMap中的key-value對老是處於有序狀態,無須專門進行排序操做
Copyright (c) 2014 LittleHann All rights reserved