引言
- 程序通常在運行時纔會根據給定的條件去創建對象,但在此之前程序是無法確定所需對象的數量和確切類型。因此要解決在任意時刻和任意位置創建對象,不能僅依靠創建命名的引用來持有每一個對象,因爲我們無法確定它的數量。
數組也許是保存對象(實際是對象的引用)的一種最有效的方式,但是數組侷限在於它是固定大小的。而java使用類庫提供了一套完整的集合類來解決這個問題。
下面給出java集合類通常會碰到的接口和類的關係圖:
圖中的虛線框表示接口,實線框表示類。帶有空心箭頭的虛線表示一個類實現某個特定接口,如:ArrayList實現了List接口;實心箭頭虛線表示某個類可以生成箭頭所指類的對象,如:任意的Collection可以生成Iterator,List可以生成ListIterator。
粗線框所圈出來的是我們常用四個集合類
集合類的劃分
1) Collection
一組”對立”的元素,通常這些元素都服從某種規則
1.1) List必須保持元素特定的順序
1.2) Set不能有重複元素
2) Map
一組成對的”鍵值對”對象
Collection和Map的區別:
1) Collection 每個位置只能保存一個元素(對象)
2) Map保存的是”鍵值對”,就像一個小型數據庫。我們可以通過”鍵”找到該鍵對應的”值”。
第一部分 Collection
1、List
1)創建對象
List本身作爲一個接口不能直接創建對象的,但是可以利用實現它的ArrayList類和LinkedList類來創建對象。另外在使用集合時,我們必須要用到泛型來指定存入集合類當中的參數類型。如果不用泛型指定,那麼返回的元素類型將是Object類型,必須要進行類型強轉才能得到存入集合之前的類型的元素
1.1)直接創建
//向上轉型,它的弊端就是對象引用不能調用ArrayList當中特有的方法
List<String> alist = new ArrayList<String>();
List<String> llist = new LinkedList<String>();
ArrayList<String> al = new ArrayList<String>();
LinkedList<String> ll = new LinkedList<String>();
1.2)利用方法創建
在java,util包中,Arrays類的靜態方法asList() 接受一個數組或者逗號分開的元素列表(使用可變參數)並將其轉換爲一個List對象,但由於他的底層是數組實現,所以由它產生的List對象具有固定大小。
List<String> list = Arrays.asList("java collection".split(" "));
List<Integer> listInt = Arrays.asList(1,2,3);
1.3)ArrayList和LinkedList的構造器
1.3.1)ArrayList有三種構造器:
ArrayList()//無參構造器
ArrayList(Collection<? extends E> c)//該構造器接受一個指定元素類型的Collection對象
ArrayList(int initialCapacity)//int型的參數指定ArrayList對象的初始大小
1.3.2)LinkedList的兩種構造器:
LinkedList()
LinkedList(Collection<? extends E> c)
2)兩種類型的List區別
ArrayList:長於隨機訪問,但在List中間插入和移除元素比較慢
LinkedList:快速地在List中間進行插入和刪除操作,但在隨機訪問方面比較慢,但它的特性集較ArrayList更大。
3)List常用的方法
整個集合類的包含的方法比較多,但幸運的是僅從這些方法的名稱中我們就很容易判斷出它們各自的功能,下面將列舉一些在List中常用的一些方法,具體使用請參見官方的API。
4)LinkedList特有方法
LinkedList當中還添加了一些方法,可以使其用作Queue或Stack
- 4.1)Stack
棧是一種後進先出的容器,在java的util包中提供了Stack類。
由於LinkedList也提供了相應的方法來支持棧的行爲,因此我們可以通過創建LinkedList對象,調用它的一些方法來實現自定義的Stack類
//用LinkedList實現
import java.util.LinkedList;
public class Stack<T> {
private LinkedList<T> storage = new LinkedList<T>();
public void push(T v){storage.addFirst(v);}
public T peek(){return storage.getFirst();}
public T pop(){return storage.removeFirst();}
public boolean empty(){return storage.isEmpty();}
public String toString(){return storage.toString();}
}
在上示代碼當中,push()將對象壓入棧。peek()和pop()都是返回棧頂元素,但不同的是,peek()並不會刪除棧頂元素,而pop()將棧頂元素刪除。
需要特別注意的是,在調用自定義棧時,一定要導入該自定義棧所在的包(若調用類和自定義棧在同一包內則不需要),否則編譯器報錯。
4.2)Queue
隊列是一種先進先出的容器。它的存入順序和取出順序是一樣的。由於它可以安全的將對象從一個任務傳輸到另一個任務當中,因此常常被用在在併發編程中。LinkedList實現了Queue接口,並且提供了方法支持隊列的行爲:
返回類型 | 方法名及參數 | 功能描述 |
---|---|---|
boolean | offer(T t) | 將元素插入隊尾 |
T | element() | 取回但不移除表頭元素 |
4.2.1)典型隊列
下面給出實現典型的Queue的代碼實例:
import java.util.*;
public class QueueDemo {
public static void main(String[] args){
//由於LinkedList實現了Queue接口,所以能完成向上轉型
Queue<String> queue = new LinkedList<String>();
String[] arr = "the test of QueueDemo".split(" ");
for(String s:arr)
queue.offer(s);
System.out.println(queue);
String getHead = queue.element();
System.out.println(getHead);
System.out.println(queue);
}
}/*output:
[the, test, of, QueueDemo]
the
[the, test, of, QueueDemo]
*/
4.2.1)PriorityQueue(優先級隊列)
典型的隊列講究的是先進先出,但是優先級隊列是根據對象的優先級順序(從高到底)來返回元素。 這裏所說的優先級實際上是一種排序規則: 當調用PriorityQueue中的offer()來插入對象時,默認的排序將使用對象在隊列中的自然順序,就如下面例子所示一樣: import java.util.PriorityQueue; public class PriorityQueueDemo { public static void main(String[] args){ PriorityQueue<String> pri = new PriorityQueue<>(); String[] arr = "the test of queuedemo".split(" "); for(String s:arr) pri.offer(s); System.out.println(pri); } } /*output: * [of, queueDemo, test, the] */ 之所以產生自然排序的情況是因爲PriorityQueue類中的offer()在添加對象時,會調用一個siftUp()方法,該方法內部調用默認的比較方法來進行比較排序。 當然PriorityQueue構造器`PriorityQueue(Comparator<? super E> comparator)`允許我們來修改默認的比較順序。
5)Set
Set最大的特點就是它不保存重複的元素,並且它與Collection具有完全相同的接口,區別僅在於它們的行爲不同。Set常被用來測試對象的歸屬性,即當前對象是否包含在內。
實現Set的常用容器類分別有HashSet,LinkedHashSet,TreeSet
類名稱 | 區別描述 |
---|---|
HashSet | 使用散列函數優化了查找速度,元素輸出無規律 |
LinkedHashSet | 使用散列函數優化了查找速度,但維持了插入順序 |
TreeSet | 將元素存儲在紅-黑樹數據結構當中 |
由於Set除了Collection中所有的方法法外沒有額外的功能,因此對於它的方法調用此處不再贅述,請讀者親試。
第二部分 Map
Map實際上實現的是不同對象之間的一種映射關係。這種映射關係是通過鍵-值思想來維護。因此我們可以通過鍵(一個對象)查找對應的值(另一個對象)。
1、Map的實現
同Collection一樣,Map是個接口,通過它本身是無法創建對象的。但是我們可以通過實現該接口的類來創建Map型對象。
1)HashMap
hashCode()是根類Object當中的方法,因此對於所有java對象來說都能產生哈希碼來標明自己獨一無二的身份。HashMap正是根據每個對象的哈希碼(散列碼)進行快速查詢的,這種方式能夠顯著地提高性能。
HashMap當中常用的一些方法
返回類型 | 方法名及參數 | 功能描述 |
---|---|---|
void | clear() | 清除Map中所有鍵值對 |
boolean | containsKey(Object key) | 判斷當前Map對象中是否存在參數所傳入的鍵 |
boolean | containsValue(Object value) | 判斷當前Map對象中是否存在參數所傳入的值 |
Set<Map.Entry<K,V>> |
entrySet() | 返回一個Map的視圖 |
V | get(Object key) | 根據鍵查找值 |
Set<K> |
keySet() | 返回由當前Map中的鍵所組成的Set |
V | put(K key, V value) | 向Map中添加鍵值對 |
V | replace(K key, V value) | 只替換指定鍵所關聯的值 |
下面給出簡單的代碼示例:
import java.util.*;
public class Maps {
public static void main(String[] args){
HashMap<Integer,String> map = new HashMap<Integer,String>();
map.put(1, "dog");
map.put(2, "sheep");
map.put(3, "duck");
System.out.println("hashmap:"+map);
System.out.println("clone():"+map.clone());
System.out.println(map.isEmpty());
Set<Map.Entry<Integer, String>> set = map.entrySet();
Iterator<Map.Entry<Integer, String>> it = set.iterator();
while(it.hasNext())
System.out.print(it.next()+" ,");
System.out.println(map.replace(1,"chicken"));
System.out.println("HashMap:"+map);
map.clear();
System.out.println(map.isEmpty());
- 2)LinkedHashMap
LinkedHashMap爲了提高速度,LinkedHashMap散列化了所有元素。但是在遍歷鍵值對的時候,卻又以元素的插入順序返回鍵值對(HashMap不能保證這個順序)
由於LinkedHashMap繼承自HashMap,所以其中很多方法的使用都和HashMap的用法類似,這裏就不再贅述。
在LinkedHashMap有這樣一個構造器:
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
其中initialCapacity是指定LinkedHashMap的初始容量,一般默認構造器是16;loadFactor是負載因子(負載因子是在容量自動增加之前允許哈希表得到滿足的度量。 當哈希表中的條目數超過負載因子和當前容量的乘積時,重新排列哈希表(即,內部數據結構被重新構建),以使散列表具有大約兩倍的容量。),默認構造器爲0.75;accessOrder,該屬性爲 boolean 型變量,對於訪問順序,爲 true;對於插入順序,則爲 false。一般情況下,不必指定排序模式,其迭代順序即爲默認爲插入順序。
使用該構造器,我們可以實現LRU算法(最近最少用算法),因爲構造器本身已經實現了按照訪問順序的存儲,也就是說,最近讀取的會放在最後端,最近未讀取到的放在最前端,以便定期清除。
下面給出簡單實例:
import java.util.*;
public class Maps_2 {
public static void main(String[] args) {
LinkedHashMap<Integer, String> linkedMap =
new LinkedHashMap<>(16, 0.75f, true);
linkedMap.put(0, "dog");
linkedMap.put(1, "sheep");
linkedMap.put(2, "duck");
linkedMap.put(3, "fish");
//原存儲順序
System.out.println("before:"+linkedMap);
//元素訪問
linkedMap.get(0);
//訪問後的存儲順序
System.out.println("after:"+linkedMap);
}
}
/*output:
before:{0=dog, 1=sheep, 2=duck, 3=fish}
after:{1=sheep, 2=duck, 3=fish, 0=dog}
*/
3)TreeMap
實際TreeMap是基於數據結構當中的紅黑樹實現的,它的特點在於經它所得到的結果是有序的(這個次序取決於Comparable或Comparator) 如果說某個自定義類要被用作TreeMap的鍵,則必須要實現Comparable接口,該接口下僅有一個方法,即:compareTO()
import java.util.*;
class Student implements Comparable<Student> {
public String name;
public int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
// 覆蓋Comparable僅有的compareTo方法(告訴 TreeMap 如何排序)
public int compareTo(Student o) {
if (o.score < this.score) {
return 1;
} else if (o.score > this.score) {
return -1;
}
return 0;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("name:");
sb.append(name);
sb.append(" ");
sb.append("score:");
sb.append(score);
return sb.toString();
}
public static void main(String[] args) {
TreeMap<Student,StudentDetailInfo> map = new TreeMap<>();
Student s1 = new Student("1", 100);
Student s2 = new Student("2", 99);
Student s3 = new Student("3", 97);
Student s4 = new Student("4", 91);
map.put(s1, new StudentDetailInfo(s1));
map.put(s2, new StudentDetailInfo(s2));
map.put(s3, new StudentDetailInfo(s3));
map.put(s4, new StudentDetailInfo(s4));
// 打印分數位於 S4 和 S2 之間的人
Map<Student,StudentDetailInfo> map1 = ((TreeMap<Student,StudentDetailInfo>) map).subMap(s4, s2);
for (Iterator<Student> iterator = map1.keySet().iterator(); iterator.hasNext();) {
Student key = (Student) iterator.next();
System.out.println(key + "->" + map.get(key));
}
System.out.println("subMap end");
// 打印分數比 s1 低的人
map1 = ((TreeMap<Student,StudentDetailInfo>) map).headMap(s1);
for (Iterator<Student> iterator = map1.keySet().iterator(); iterator.hasNext();) {
Student key = (Student) iterator.next();
System.out.println(key + "->" + map.get(key));
}
System.out.println("subMap end");
// 打印分數比 s1 高的人
map1 = ((TreeMap<Student,StudentDetailInfo>) map).tailMap(s1);
for (Iterator<Student> iterator = map1.keySet().iterator(); iterator.hasNext();) {
Student key = (Student) iterator.next();
System.out.println(key + "->" + map.get(key));
}
System.out.println("subMap end");
}
}
class StudentDetailInfo {
Student s;
public StudentDetailInfo(Student s) {
this.s = s;
}
public String toString() {
return s.name + "'s detail information";
}
}
上示代碼中涉及到TreeMap的幾個方法:
返回類型 | 方法名及參數 | 功能描述 |
---|---|---|
SortedMap | subMap(fromKey, toKey) | 生成當前Map的子集,其範圍由fromKey(包含)到toKey(不包含)的鍵確定 |
SortedMap | headMap(toKey) | 生成當前Map子集,由鍵值小於toKey的所有鍵值對組成 |
SortedMap | tailMap(fromKey) | 生成當前Map的子集,由鍵值大於或等於fromKey的所有鍵值對組成 |
SortedMap是一個接口(TreeMap實現了該接口),它可以確保鍵處於排序狀態。
第三部分 Foreach與迭代器
無論是foreach語法還是迭代器,它們的使用一定建立在Collection對象上,而不適用於Map對象。
在本篇開頭的關係圖中,我們可以看到Collection對象可以生Iterator,這是由於Collection繼承了Iterable接口,由iterator方法產生Iterator對象調用對應的方法,從而實現迭代。
import java.util.*;
public class List_1 {
public static void main(String[] args){
List<String> list =
Arrays.asList("The test for iterator".split(" "));
//方法一
Iterator<String> it = list.iterator();
While(it.hasNext())
System.out.print(it.next());
//方法二
for(Iterator<String> it = list.iterator();it.next();)
System.out.print(it.next());
}
}
foreach語法被廣泛地應用於數組的遍歷,但是它同時也能應用於任何的Collection對象。準確地說,所有實現了Iterable的類都能被用於foreach語法。
import java.util.*;
public class List_1 {
public static void main(String[] args){
List<String> list =
Arrays.asList("The test for iterator".split(" "));
for(String s: list)
System.out.println(s);
}
}
總結
1、Collection保存單一對象,而Map則保存相關聯的鍵值對;
2、List和數組一樣都建立對象跟數字索引的關聯,List能自動擴充容量而數組具有固定大小;
3、如果要進行大量的隨機訪問用ArrayList,需要在表中間插入或者刪除元素則用LinkedList;
4、Set不接受重複元素。HashSet提供最快的查詢速度,LinkedHashSet在提高查詢速度的同時,以插入順序保存元素。TreeSet保持元素處於排序狀態。
5、Map將對象與對象之間形成關聯關係。HashMap設計用來快速方訪問,TreeMap使得鍵始終處於排序狀態。LinkedHashMap除了保持元素的插入順序外,也和HashMap一樣通過散列提高了訪問速度。