在寫程序的時候並不是每次隻使用一個對象, 更多的是對一組對象進行操作, 就需要知道如何組合這些對象, 還有在編碼的時候我們有時並不知道到底有多少對象,它們需要進行動態的分配存放。
Java的集合類隻能容納對象句柄, 對於簡單類型的數據存放, 隻能通過數據來存放, 數組可以存放簡單類型的數據也能存放對象。
Java提供了四種類型的集合類: Vector(矢量), BitSet(位集), Stack(堆棧), Hashtable(散列表)。
1. 矢量: 一組有序的元素, 可以通過index進行訪問。
2. 位集: 其實就是由二進製位構成的Vector, 用來保存大量”開-關”信息, 它所占的空間比較小, 但是效率不是很高, 如果想高效率訪問, 還不如用固定長度的數組。
3. 堆棧: 先入後出(LIFO)集合, java.util.Stack類其實就是從Vector繼承下來的, 實現了pop, push方法。
4. 散列表: 由一組組“鍵--值”組成, 這裡的鍵必須是Object類型。 通過Object的hashCode進行高效率的訪問。
對於這些集合之間的關聯關係見下圖, 其中標色的部分為我們常用的類。
由上圖可以看出, 基本接口有兩個:
Collection: 所有的矢量集合類都從它繼承下去的, 但並不直接從它繼承下去的。 List與Set這兩個接口直接繼承了Collection, 他們的區彆是List裡麵可以保存相同的對象句柄, 而Set裡麵的值是不重複的。 我們經常用的Vector與ArrayList就是從List繼承下去的, 而HashSet是從Set繼承的。
Map:散列表的接口, Hashtable與HashMap繼承了這個接口。
下麵給出常用集合類的常用方法。
/**
* Vector 與 ArrayList的操作幾乎是一樣的
* 常用的追加元素用add(), 刪除元素用remove()
* 取元素用get(), 遍曆它可以循環用get()取. 或者
* 先得到一個Iterator, 然後通過遍曆Iterator的方法
* 遍曆Vector或ArrayList
*/
// 生成一個空的Vector
Vector vector = new Vector();
// 在最後追加一個元素。
vector.add("one");
vector.add("two");
// 在指定的地方設置一個值
vector.set(0, "new one");
// 移走一個元素或移走指定位置的元素
vector.remove(0);
// 用for循環遍曆這個Vector
for (int i = 0; i < vector.size(); i++) {
String element = (String) vector.get(i); }
// 用枚舉器(Enumeration)遍曆它(隻有Vector有,ArrayList冇有)
Enumeration enu = vector.elements();
while (enu.hasMoreElements()) {
enu.nextElement();}
// 用反複器(Iterator)遍曆它
Iterator it = vector.iterator();
while (it.hasNext()) {it.next();}
/**
* Hashtable與HashMap的操作, 追加元素用put(不是add)
* 刪除元素用remove, 遍曆可以用Iterator 既可以遍曆
* 它的key, 也可以是value
*/
// 生成一個空的Hashtable或HashMap
Hashtable hashtable = new Hashtable();
// 追加一個元素
hashtable.put("one", "one object value");
// 刪除一個元素
hashtable.remove("one");
// 用Iterator遍曆
Iterator keyIt = hashtable.keySet().iterator();
while (keyIt.hasNext()) {
Object keyName = keyIt.next();
String value = (String) hashtable.get(keyName); }
Iterator valueIt = hashtable.values().iterator();
while (valueIt.hasNext()) {
valueIt.next();}
// 用Enumeration遍曆, 隻有Hashtable有, HashMap冇有.
Enumeration enu = hashtable.elements();
while (enu.hasMoreElements()) {
enu.nextElement();}
說明: Enumeration是老集合庫中的接口, 而Iterator是新集合(1.2)中出現的, 而Vector與Hashtable也都是老集合中的類, 所以隻有Vector與Hashtable可以用Enumeration。
Vector與ArrayList對比:
雖然在使用的時候好象這兩個類冇什麼區彆, 它們都是從List繼承下來的, 擁有相同的方法, 但它們的內部還是有些不同的,
Ø 首先Vector在內部的一些方法作了線程同步(synchronized)。 同步的代價就是降低了執行效率, 但提高了安全性。而ArrayList則是線程不同步的, 可以多線程並發讀寫它。
Ø 內部數據增長率。 所有的這些矢量集合在內部都是用Object的數組進行存儲和操作的。 所以也就明白了為什麼它可以接受任何類型的Object, 但取出來的時候需要進行類型再造。 Vector與ArrayList具有自動伸縮的功能, 我們不用管它size多大, 我們都可以在它的後麵追加元素。 Vector與ArrayList內部的數組增長率是不一樣的, 當內部的數組不能容納更多元素的時候, Vector會自動增長到原兩倍大小, ArrayList會變為原一倍半大小, 而不是我們所想象的一個元素一個元素的增長。
Hashtable與HashMap對比:
Hashtable與HashMap都是從Map繼承下來的, 方法幾乎都一樣, 它們內部有兩個不同點:
Ø 與Vector和ArrayList一樣, 它們在線程同步是不同的, Hashtable在內部做了線程同步, 而HashMap是線程不同步的。
Ø HashMap的鍵與值都可以為null, 而Hashtable不可以, 如果你試圖將一個null值放到Hashtable裡麵去, 會拋一個NullPointException的。
性能對比:
拋開不常用的集合不講, 每種集合都應該有一個我們常用的集合類, 而在不同的場合下應該使用效率最高的一個。 一般來說我推薦儘量使用新的集合類, 除非不得已, 比如說需要用用了老集合類寫的產品的程序。 也就是說儘量使用ArrayList與HashMap, 而少使用Vector與Hashtable。
Ø 在單線程中使用ArrayList與HashMap, 而在多線程中如果需要進行線程同步可以使用Vector與Hashtable, 但也可以用synchronized對ArrayList與HashMap進行同步, 不過同步後的ArrayList與HashMap是比Vector與Hashtable慢的。 不過我認為需要進行線程同步的地方並不多。 如果一個變量定義在方法內部同時隻可能有一個線程對之進行操作, 就不必要進行同步, 如果定義在類的內部並且不是靜態的, 屬於實例變量, 而這個類並冇有被多線程使用也就不必要同步。
一般自己寫的程序很少會自己去另開線程的, 但在Web開發的時候, 如果用了Servlet, 則每個request都是一個線程, 也就是說每個Servlet都是在多線程環境下運行的, 如果Servlet中使用了全局靜態的成員變量就得小心點兒, 如果需要同步就得在方法上加上synchronized修飾符, 如果允許多個線程操作它, 並且你知道不會有什麼衝突問題就可以大膽的使用ArrayList與HashMap。 另外如果在多線程中有線程在對ArrayList或HashMap進行修改(結構上的修改), 而有一個線程在用Iterator進行讀取操作, 這個時候就有可能會拋ConcurrentModificationException, 因為用Iterator的時候, 不允許原List的結構改變。但可以用get方法來取。
常用技巧:
1. 采用麵向接口的編程技巧, 比如現在需要寫一個共通函數,對矢量集合類諸如Vector,ArrayList,HashSet等等進行操作, 但我並不知道最終用戶會具體傳給我什麼類型的類, 這個時候我們可以使用Collection接口, 從而使代碼具有很大的靈活性。 代碼示例如下:
/**
* 將list裡麵的所有元素用sep連接起來,
* list可以為Vector, ArrayList, HashSet等。
*/
public static String join(String sep, Collection list) {
StringBuffer sb = new StringBuffer();
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
sb.append(iterator.next());
if (iterator.hasNext()) { sb.append(sep); }
}
return sb.toString();
}
2. 利用Set進行Unique, 比如有一組對象(其中有對象是重複的), 但我們隻對不同的對象感興趣, 這個時候可以使用HashSet這個集合類, 然後可以通過覆蓋Object的equals方法來選擇自定義判斷相等的rule。 缺省的是地址判斷。 例:
class DataClass {
private String code = null;
private String name = null;
public void setCode(String code) {this.code = code; }
public String getCode() {return this.code; }
public void setName(String name) {this.name = name; }
public String getName() {return this.name; }
public boolean equals(DataClass otherData) {
if (otherData != null) {
if (this.getCode() != null&& this.getCode().equals(otherData.getCode()) {
return true;
}
}
return false;
}}
DataClass data1 = new DataClass();
DataClass data2 = new DataClass();
data1.setCode("1");
data2.setCode("1");
HashSet singleSet = new HashSet();
singleSet.add(data1);
singleSet.add(data2);
結果singleSet裡麵隻有data1, 因為data2.equals(data1), 所以data2並冇有加進去。
3. 靈活的設計集合的存儲方式, 以獲得較高效的處理。 集合裡麵可以再嵌套集合, 例:在ArrayList裡麵存放HashMap, HashMap裡麵再嵌套HashMap。