我們都知道,當想要保存一組基本類型數據時,數組是最有效的保存方式,也是推薦使用這種方式的。但是數組是固有大小的,當運行時才知道大小的程序,這種方式使用就受限制了,這就是Java容器類產生的原因。Java集合類有幾個特點:首先,這種容器是高性能的,對基本數據集合(動態數組、鏈接表、樹和散列表)的實現是高效率的。第二,容器類允許不同類型的類集合以相同的方式和高度互操作方式工作。第三,容器類是容易擴展或修改的。容器類的常用的基本類型有List、Set和Map,這些對象類型也稱為集合類,但是在Java中使用了Collection這個名字來指代該類庫的一個特殊子集,所以業界使用了范圍更廣泛的“容器”來稱呼。
Collection:是一個接口,它位於集合框架層次結構的頂層,繼承自Iterable接口,說明是可以用Iterator迭代器來訪問該集合中的元素的。又有List、Set和Queue接口繼承Collection接口,直接實現該接口的是一個叫AbstractCollection的抽象類,該抽象類以最大限度地減少了實現此接口所需的工作。
List:繼承自Collection接口,表示有序的、可包括重復元素的列表。同時擁有Collection內的方法外,還添加了大量的方法,使得可以在List的中間插入和刪除元素。實現該接口的基本類有ArrayList和LinkedList. ArrayList:擅長於對元素的隨機訪問,但是在插入和刪除元素時效率較慢。其實,看看ArrayList類實現的源代碼就知道,ArrayList是以線性表的數據結構形式存取數據的,初始化的表大小為10,下面就有幾個經常用到的核心方法:add(E e):在當前表的末尾插入元素,如果在前面表不滿的情況下,也是很高效的,直接插入到末尾,但是如果在當前表已經滿的情況下,就要重新生成一個比當前表大小更大的新表,新表的大小是當前表大小的1.5倍加1,比如當前表長度為20的,新表的大小就為31,還需要把當前表元素復制到新表中去,然後把當前表引用指向新表,最後把數值插入到表末尾,所以這種操作是非常低效的。
add(int index,E element):在指定索引位置插入元素,檢查表大小和重新追加表大小和上面的add(E e)方式是一樣的。最後是要把index以後的元素都是要依次往後移一個大小,然後把元素插入到index位置上去。涉及到表的復制和表內元素的移動,所以效率也是比add(E e)方法還要低。
remove(int index):在指定索引位置刪除元素,就是把index位置後的所有元素都往前移一個大小,也是涉及到表內元素的移動,效率也是很低的。
remove(Object o):刪除指定的元素,也就需要查找出該元素在表中出現第一次的位置,查找是用到順序一個一個進行匹配的方法,找出後就把該元素後面的所有元素往前移一個大小。該方法涉及到順序查找和表內元素移動,比remove(int index)方法更低效。
set(int index,E element):替換表中索引為index的元素值,返回被替換的值,直接用下標索引訪問元素,所以效率非常高。
get(int index):獲取索引為index的元素,直接用下標索引訪問,所以效率也是非常高。
indexOf(Object o):獲取元素的索引號,也就是需要查找,雖然用到了順序查找法,但效率還是比較高的。
LinkedList:擅長於對元素的插入和刪除操作,但對於隨機訪問元素比較慢。該類的實現是以雙向鏈表的數據結構為基礎的,所以是比較消耗內存的,但它的特定集比ArrayList更大。雙向鏈表,每個節點都有三個域,兩個域是存放前後節點的內存地址引用的,一個域是存放數據元素的。在LinkedList類中,有一個叫Entry的內部類,是private的,裡面三個屬性,分別是element、next和previous,分別對應了雙向鏈表中的三個域,在ArrayList類中每實例化一個Entry就生成一個節點。下面看看它的核心方法:add(E e):把元素插入到鏈表末尾,首先要實例化一個節點,新節點previous域存放鏈表中最後一個節點地址,next域存放鏈表中第一個節點地址,element域存放元素值,鏈表中最後一個節點的next域存放新節點的地址,第一個元素的previous域存放新節點的地址,這樣這個元素就插入到該鏈表中去了,沒有涉及到復雜的操作,所以是非常高效的。
add(int index,E element):在index位置插入元素,這就需要先查找到該位置。查到後,這裡就把查到的節點的前一個節點叫為A,實例化新的節點為B,查到index的節點為C.B的next域等於A的next值(也就是C的內存地址),B的previous域等於C的previous值(也就是A的內存地址),B的element域存放元素值,然後把A的next域和C的previous域都等於B的內存地址。這樣也就把元素插入到鏈表的index位置中去了,但涉及到了查詢,所以效率雖然高,但也沒有add(E e)那麼高。
remove(int index):刪除在index位置的元素,首先也是要找到該位置的節點。然後把該節點的下一個節點(也就是該節點next域的內存地址那個節點)的previous值等於該節點的previous值,該節點的上一個節點(也就是該節點previous域的內存地址那個節點)的next值等於該節點的next值。這樣就把該節點從這條鏈表刪除了,過程中雖然涉及到了查找,但沒有涉及到像ArrayList類中的remove方法要移動表中元素,所以該方法的效率還是很高的。
remove(Object o):刪除在鏈表中第一個元素為o的節點,也是需要查找到該節點,然後就跟remove(int index)思路一樣把元素刪除,所以效率也是很高的。
set(int index,E element):把在鏈表中第index個元素值改為element,這也需要找到該節點來修改元素值,但涉及到了查找節點,ArrayList中的set方法就不用查找就可以修改,所以相對於ArrayList中的set方法,LinkedList方法set方法效率就沒那麼高了。
get(int index):獲取第index位置的元素值,也是要找到該節點,所以就也沒ArrayList中的get方法那麼高效率了,因為該方法需要查找鏈表。
indexOf(Object o):獲取該鏈表中第一o元素的位置,也是要查找鏈表,但ArrayList中的indexOf方法也是需要查找的,所以這兩個類的indexOf的效率都差不多。
所以,在編程中,如果要進行大量的隨機訪問,就使用ArrayList;如果要經常從表中插入或刪除元素的就應該使用LinkedList.
Set:繼承自Collection接口,表示無序的,無重復元素的集合。Set中最常使用的是測試歸屬性,可以很容易測試某個對象是否在某個Set中。所以,查找就成為了Set中最重要的操作,因此通常會選擇一個HashSet的實現查找,因為有比較復雜的哈希表支持,它專門對快速查找進行了優化。
迭代器:迭代器是一種設計模式,在這裡是一個對象,它的作用就是遍歷並選擇列表和操作列表中的對象。迭代器的創佳的代價小,所以通常被稱為輕量級對象。迭代器統一了對各種容器的訪問方式,很方便。Java中的迭代器有兩種,一種是Iterator,另一種是繼承了Iterator只能用於各種List訪問的ListIterator. Iterator:只能用於單向移動,方法有:iterator()要求容器返回一個Iterator,Iterator將准備好返回序列的第一元素。next()獲得列表中的下一個元素。hasNext()檢查列表中是否還有元素。remove()將迭代器新近返回的元素刪除。
ListIterator:只能用於各種的List類的訪問,但能用於雙向的移動,有一個hASPrevious()檢查時候有前一個元素的,這種操作很像數據庫的游標。
import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.Collection;
import Java.util.Iterator;
import Java.util.LinkedList;
import Java.util.List;
import Java.util.ListIterator;
public class ListTest ...{
public static void main(String [] args)
...{
Collection<Integer> col = new ArrayList<Integer>(Arrays.asList(10,20,30));
List<Integer> list = new LinkedList<Integer>();
list.addAll(col);
list.add(40);
list.add(50);
list.add(60);
displayIterator(list);
list.remove(3);
displayListIterator(list);
}
public static void displayIterator(Collection<Integer> list)
...{
Iterator<Integer> it = list.iterator();
Integer i;
while(it.hasNext())
...{
i = it.next();
System.out.print(i + " ");
if(i==50)
...{
it.remove();
}
}
System.out.println();
}
public static void displayListIterator(List<Integer> list)
...{
ListIterator<Integer> li = list.listIterator();
/** *//**以下注釋代碼為死循環,永遠輸入表中的第一個數據*/
/** *//**while(li.hasNext())
{
System.out.println(li.next());
System.out.println(li.previous());
}*/
while(li.hasNext())
...{
System.out.print(li.next() + " ");
}
System.out.println();
while(li.hASPrevious())
...{
System.out.print(li.previous() + " ");
}
}
}
Map:也是一個映射存儲鍵/值對的接口,但跟Collection沒有任何關系的,也沒有繼承任何接口,所以不能用Iterator迭代器來訪問該集合中的元素。給定一個關鍵字和一個值,可以存儲這個值到一個Map對象中,存儲以後,就可以使用它的關鍵字來檢索它。映射經常使用到的兩個基本操作:get()和put()。使用put()方法可以將一個指定了關鍵字和值的項加入映射。為了得到值,可以通過將關鍵字作為參數來調用get()方法。
import Java.util.HashMap;
import Java.util.Map;
public class TestMap ...{
public static void main(String [] args)
...{
Map<String,Integer> hm = new HashMap<String,Integer>();
hm.put("a1", 1);
hm.put("b2", 2);
hm.put("c3", 3);
hm.put("d4", 4);
hm.put("e5", 5);
display(hm);
System.out.println(hm.containsKey("c3"));
hm.remove("c3");
System.out.println(hm.containsValue(3));
System.out.println(hm.size());
}
public static void display(Map<String,Integer> m)
...{
for(String s : m.keySet())
...{
System.out.println(s + " : " + m.get(s));
}
}
}