1. 程式人生 > >java集合架構詳解

java集合架構詳解

綜述

結合框架體系應該最重要的是如何靈活利用三種介面,set,map,list,他們如何遍歷,各有什麼特徵,怎麼樣去處理,這是關鍵,在這個基礎上再去掌握在什麼場合用什麼型別的介面。比如說list和set,set是無序的一個空袋子,當我們只需要放入取出,這個介面當然是最實用的,但是如果我們需要按序取出,這個方法就不能用了,而要用到list,map介面就有個特點,它有一個特定的key值,而一個key值又對應著一個value,這個value值中就可以存入很多的東西了,比如姓名,出生年月,性別等,而且可以指定key取出對應的value!(自寫)

如果涉及到堆疊,佇列等操作,應該考慮用List,對於需要快速插入,刪除元素,應該使用LinkedList,如果需要快速隨機訪問元素,應該使用ArrayList。

如果程式在單執行緒環境中,或者訪問僅僅在一個執行緒中進行,考慮非同步的類,其效率較高,如果多個執行緒可能同時操作一個類,應該使用同步的類。要特別注意對雜湊表的操作,作為key的物件要正確複寫equals和hashCode方法。儘量返回介面而非實際的型別,如返回List而非ArrayList,這樣如果以後需要將ArrayList換成LinkedList時,客戶端程式碼不用改變。這就是針對抽象程式設計。

一、系統簡述:

1.集合框架總體結構

Java中集合類定義主要是java.util.*包下面,常用的集合在系統中定義了三大介面,這三類的區別是

java.util.Set介面及其子類,set提供的是一個無序的集合;

java.util.List介面及其子類,List提供的是一個有序的集合;

java.util.Map介面及其子類,Map提供了一個對映(對應)關係的集合資料結構;

另外,在JDK5中新增了Queue(佇列)介面及其子類,提供了於佇列的集合體系。每種集合,都可以理解為用來在記憶體中存放一組物件的某種容器“---就像陣列,就像前面我們自己定義的佇列。

2.Set介面和List介面

Set 是最簡單的一種集合,它的物件不按特定方式排序,只是簡單的把物件加入集合中,就像往口袋裡放東西。對集中成員的訪問和操作是通過集中物件的引用進行的,所以集中不能有重複物件;而LIst的主要特徵是其物件以線性方式儲存,沒有特定順序,只有一個開頭和一個結尾,當然,它與根本沒有順序的集是不同的。列表在資料結構中分別表現為:陣列和向量、連結串列、堆疊、佇列。關於實現列表的集合類,是我們日常工作中經常用到的;從類圖中可以看到,Set 和List都是繼承了Collection介面的子介面,而這類集合都有自己的實現;

3.java.util.Map介面

現實生活中,我們常會看到這樣的一種集合:IP地址與主機名,身份證號與個人,系統使用者用與系統使用者物件等,這種一一對應的關係,就叫做對映。Java提供了專門的集合類用來存放這種物件關係的物件,即java.util.Map介面。Map是一個介面,有多種具體的實現類,常用的有HashMap和 Hashtable類實現。Map中存入的物件是一對一對的,即每個物件和它的一個名字(鍵)關聯在一起,Map中資料存放的結構如下圖示:

Key(鍵或名)

Value(key對應的值)

“userName1”

UserInfo物件1

“userName1”

UserInfo物件1

“userName1”

UserInfo物件1

     . . .

. . .

可以看出,Map中存放的是兩種物件,一種稱為key(),一種稱為value(),它們在在Map中是一一對應關係,這一對物件又稱做Map中的一個Entry()Map中的鍵不能重複,但值可以重複。(借鑑藍傑,>>>)

二、詳細闡述:

一、概述

資料結構對程式設計有著深遠的影響,在面向過程的C語言中,資料庫結構用struct來描述,而在面向物件的程式設計中,資料結構是用類來描述的,並且包含有對該資料結構操作的方法。

在Java語言中,Java語言的設計者對常用的資料結構和演算法做了一些規範(介面)和實現(具體實現介面的類)。所有抽象出來的資料結構和操作(演算法)統稱為Java集合框架(Java Collection Framework)。

Java程式設計師在具體應用時,不必考慮資料結構和演算法實現細節,只需要用這些類創建出來一些物件,然後直接應用就可以了。這樣就大大提高了程式設計效率。

二、集合框架的層次結構

Collection是集合介面

|――――Set子介面:無序,不允許重複。

|――――List子介面:有序,可以有重複元素。

區別:Collections是集合類

Set和List對比:

Set:檢索元素效率低下,刪除和插入效率高,插入和刪除不會引起元素位置改變。

List:和陣列類似,List可以動態增長,查詢元素效率高,插入刪除元素效率低,因為會引起其他元素位置改變。

Set和List具體子類:

Set

|――――HashSet:以雜湊表的形式存放元素,插入刪除速度很快。

List

|――――ArrayList:動態陣列

|――――LinkedList:連結串列、佇列、堆疊。

Array和java.util.Vector

Vector是一種老的動態陣列,是執行緒同步的,效率很低,一般不贊成使用。

三、Iterator迭代器(介面)

Iterator是獲取集合中元素的過程,實際上幫助獲取集合中的元素。

迭代器代替了 Java Collections Framework 中的 Enumeration。迭代器與列舉有兩點不同:

迭代器允許呼叫方利用定義良好的語義在迭代期間從迭代器所指向的集合移除元素。

方法名稱得到了改進。

Iterator 僅有一個子介面ListIterator,是列表迭代器,允許程式設計師按任一方向遍歷列表、迭代期間修改列表,並獲得迭代器在列表中的當前位置。 ListIterator 沒有當前元素;它的游標位置 始終位於呼叫 previous() 所返回的元素和呼叫 next() 所返回的元素之間。在長度為 n 的列表中,有 n+1 個有效的索引值,從 0 到 n(包含)。

四、集合框架之外的Map介面

Map將鍵對映到值的物件。一個對映不能包含重複的鍵;每個鍵最多隻能對映一個值。

Map介面是Dictionary(字典)抽象類的替代品。

Map 介面提供三種collection 檢視,允許以鍵集、值集合或鍵-值對映關係集的形式檢視某個對映的內容。對映的順序 定義為迭代器在對映的 collection 檢視中返回其元素的順序。某些對映實現可明確保證其順序,如 TreeMap 類;某些對映實現則不保證順序,如 HashMap 類。

有兩個常見的已實現的子類:

HashMap:基於雜湊表的 Map 介面的實現。此實現提供所有可選的對映操作,並允許使用 null 值和 null 鍵。(除了不同步和允許使用 null 之外,HashMap 類與 Hashtable 大致相同。)此類不保證對映的順序,特別是它不保證該順序恆久不變。

TreeMap:它實現SortedMap 介面的基於紅黑樹的實現。此類保證了對映按照升序順序排列關鍵字,根據使用的構造方法不同,可能會按照鍵的類的自然順序 進行排序(參見 Comparable),或者按照建立時所提供的比較器進行排序。

Hashtable:此類實現一個雜湊表,該雜湊表將鍵對映到相應的值。任何非 null 物件都可以用作鍵或值。

五、執行緒安全類

在集合框架中,有些類是執行緒安全的,這些都是JDK1.1中的出現的。在JDK1.2之後,就出現許許多多非執行緒安全的類。

下面是這些執行緒安全的同步的類:

Vector:就比ArrayList多了個同步化機制(執行緒安全)。

Statck:堆疊類,先進後出。

Hashtable:就比HashMap多了個執行緒安全。

Enumeration:列舉,相當於迭代器。

除了這些之外,其他的都是非執行緒安全的類和介面。

執行緒安全的類其方法是同步的,每次只能一個訪問。是重量級物件,效率較低。對於非執行緒安全的類和介面,在多執行緒中需要程式設計師自己處理執行緒安全問題。

六、其他一些介面和類介紹

Dictionary和Hashtable類:

Dictionary提供鍵值對映的功能,是個抽象類。一般使用它的子類HashTable類。遍歷Hashtable類要用到列舉。

Properties類

Properties 繼承於 Hashtable,Properties 類表示了一個持久的屬性集。Properties 可儲存在流中或從流中載入。屬性列表中每個鍵及其對應值都是一個字串。一般可以通過讀取properties配置檔案來填充Properties物件。

七、各個介面的闡述

1.Collection介面 

Collection是最基本的集合介面,一個Collection代表一組Object,即Collection的元素(Elements)。一些 Collection允許相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接繼承自Collection的類,Java SDK提供的類都是繼承自Collection的“子介面”如List和Set。 

所有實現Collection介面的類都必須提供兩個標準的建構函式:無引數的建構函式用於建立一個空的Collection,有一個 Collection引數的建構函式用於建立一個新的Collection,這個新的Collection與傳入的Collection有相同的元素。後一個建構函式允許使用者複製一個Collection。 

如何遍歷Collection中的每一個元素?不論Collection的實際型別如何,它都支援一個iterator()的方法,該方法返回一個迭代子,使用該迭代子即可逐一訪問Collection中每一個元素。典型的用法如下: 

Iterator it = collection.iterator(); // 獲得一個迭代子 

while(it.hasNext()) { 

Object obj = it.next(); // 得到下一個元素 

由Collection介面派生的兩個介面是List和Set。 

2.List介面 

List是有序的Collection,使用此介面能夠精確的控制每個元素插入的位置。使用者能夠使用索引(元素在List中的位置,類似於陣列下標)來訪問List中的元素,這類似於Java的陣列。 

和下面要提到的Set不同,List允許有相同的元素。 

除了具有Collection介面必備的iterator()方法外,List還提供一個listIterator()方法,返回一個 ListIterator介面,和標準的Iterator介面相比,ListIterator多了一些add()之類的方法,允許新增,刪除,設定元素,還能向前或向後遍歷。 

實現List介面的常用類有LinkedList,ArrayList,Vector和Stack。 

3.LinkedList類

LinkedList實現了List介面,允許null元素。此外LinkedList提供額外的get,remove,insert方法在 LinkedList的首部或尾部。這些操作使LinkedList可被用作堆疊(stack),佇列(queue)或雙向佇列(deque)。 

注意LinkedList沒有同步方法。如果多個執行緒同時訪問一個List,則必須自己實現訪問同步。一種解決方法是在建立List時構造一個同步的List: 

List list = Collections.synchronizedList(new LinkedList(...)); 

4.ArrayList類

ArrayList實現了可變大小的陣列。它允許所有元素,包括null。ArrayList沒有同步。 

size,isEmpty,get,set方法執行時間為常數。但是add方法開銷為分攤的常數,新增n個元素需要O(n)的時間。其他的方法執行時間為線性。 

每個ArrayList例項都有一個容量(Capacity),即用於儲存元素的陣列的大小。這個容量可隨著不斷新增新元素而自動增加,但是增長演算法並沒有定義。當需要插入大量元素時,在插入前可以呼叫ensureCapacity方法來增加ArrayList的容量以提高插入效率。 

和LinkedList一樣,ArrayList也是非同步的(unsynchronized)。 

5.Vector類

Vector非常類似ArrayList,但是Vector是同步的。由Vector建立的Iterator,雖然和ArrayList建立的 Iterator是同一介面,但是,因為Vector是同步的,當一個Iterator被建立而且正在被使用,另一個執行緒改變了Vector的狀態(例如,新增或刪除了一些元素),這時呼叫Iterator的方法時將丟擲ConcurrentModificationException,因此必須捕獲該異常。 

6.Stack 類 

Stack繼承自Vector,實現一個後進先出的堆疊。Stack提供5個額外的方法使得Vector得以被當作堆疊使用。基本的push和pop方法,還有peek方法得到棧頂的元素,empty方法測試堆疊是否為空,search方法檢測一個元素在堆疊中的位置。Stack剛建立後是空棧。 

7.Set介面 

Set是一種不包含重複的元素的Collection,即任意的兩個元素e1和e2都有e1.equals(e2)=false,Set最多有一個null元素。 

很明顯,Set的建構函式有一個約束條件,傳入的Collection引數不能包含重複的元素。 

請注意:必須小心操作可變物件(Mutable Object)。如果一個Set中的可變元素改變了自身狀態導致Object.equals(Object)=true將導致一些問題。 

8.Map介面 

請注意,Map沒有繼承Collection介面,Map提供key到value的對映。一個Map中不能包含相同的key,每個key只能對映一個 value。Map介面提供3種集合的檢視,Map的內容可以被當作一組key集合,一組value集合,或者一組key-value對映。 

9.Hashtable類 

Hashtable繼承Map介面,實現一個key-value對映的雜湊表。任何非空(non-null)的物件都可作為key或者value。 

新增資料使用put(key, value),取出資料使用get(key),這兩個基本操作的時間開銷為常數。 

Hashtable 通過initial capacity和load factor兩個引數調整效能。通常預設的load factor 0.75較好地實現了時間和空間的均衡。增大load factor可以節省空間但相應的查詢時間將增大,這會影響像get和put這樣的操作。 

使用Hashtable的簡單示例如下,將1,2,3放到Hashtable中,他們的key分別是”one”,”two”,”three”: 

Hashtable numbers = new Hashtable(); 

numbers.put(“one”, new Integer(1)); 

numbers.put(“two”, new Integer(2)); 

numbers.put(“three”, new Integer(3)); 

要取出一個數,比如2,用相應的key: 

Integer n = (Integer)numbers.get(“two”); 

System.out.println(“two = ” + n); 

由於作為key的物件將通過計算其雜湊函式來確定與之對應的value的位置,因此任何作為key的物件都必須實現hashCode和equals方法。hashCode和equals方法繼承自根類Object,如果你用自定義的類當作key的話,要相當小心,按照雜湊函式的定義,如果兩個物件相同,即obj1.equals(obj2)=true,則它們的hashCode必須相同,但如果兩個物件不同,則它們的hashCode不一定不同,如果兩個不同物件的hashCode相同,這種現象稱為衝突,衝突會導致操作雜湊表的時間開銷增大,所以儘量定義好的hashCode()方法,能加快雜湊表的操作。 

如果相同的物件有不同的hashCode,對雜湊表的操作會出現意想不到的結果(期待的get方法返回null),要避免這種問題,只需要牢記一條:要同時複寫equals方法和hashCode方法,而不要只寫其中一個。