JAVA對象容器

轉自:http://www.blogjava.net/juhongtao/archive/2005/12/09/23103.html

數組與其它容器的區別體現在三個方面:效率,類型識別以及可以持有primitives.

數組是java提供的,是能隨機存儲和訪問reference序列的諸多方法中,最高效的一種。數組是線形序列,所以它可以快速訪問其中的元素,但速度是有代價的,當你創建了一個數組之後它的容量就固定了,而且在其生命週期裏不能改變。也許你會提議先創建一個數組,等到快不夠用的時候,再創建一個新的,然後將舊數組裏的reference 全部導到新的裏面。其實ArrayList 就是這麼做的。但是這種靈活性所帶來的開銷,使得ArrayList 的效率比       起數組有了明顯下降。      

在我們寫程序的時候往往不知道要用多少對象,或者要用一種更復雜方式來存儲對象情況。爲此,Java 提供了“容器類(container class)”。其基本類型有List, Set 和Map。有了這些工具,你就能解決很多問題了。它們還有一些別的特性。比方說Set 所持有的對象,個個都不同,Map則是一個“關聯性數組(associative array)”,它能在兩個對象之間建立聯繫。此外,與數組不同,它們還能自動調整大小,所以你可以往裏面放任意數量的對象。這樣寫程序的時候,就不用操心要開多大的空間了。

 

Java2 的容器類要解決“怎樣持有對象”,而它把這個問題分成兩類:

1. Collection: 通常是一組有一定規律的獨立元素。List 必須按照特定的順序持有這些元素,而Set 則不能保存重複的元素。(bag沒有這個限制,但是Java的容器類庫沒有實現它,因爲List已經提供這種功能了。)

2. Map: 一組以“鍵——值”(key-value)形式出現的pair。初看上去,它應該是一個pair的Collection,但是真這麼去做的話,它就會變得很滑稽,所以還是把這個概念獨立列出來爲好。退一步說,真的要用到Map 的某個子集的時候,創建一個Collection 也是很方便的。Map可以返回“鍵(key)的”Set,值的Collection,或者pair的Set。和數組一樣,Map 不需要什麼修改,就能很容易地擴展成多維。你只要直接把Map 的值設成Map 就可以了(然後它的值再是Map,以此類推)。我們先來看看容器的一般特性,然後深入細節,最後再看什麼會有這麼多版本,以及如何進行選擇。

 

List 會老老實實地持有你所輸入的所有對象,既不做排序也不做編輯。Set 則每個對象只接受一次,而且還要用它自己的規則對元素進行重新排序(一般情況下,你關心的只是Set 包沒包括某個對象,而不是它到底排在哪裏——如果是那樣,你最好還是用List)。而Map 也不接收重複的pair,至於是不是重複,要由key來決定。此外,它也有它自己的內部排序規則,不會受輸入順序影響。如果插入順序是很重要的,那你就只能使用LinkedHashSet 或LinkedHashMap 了。

test.bmp

第一眼看到這張圖的時候,你會覺得很震撼。不過你馬上就會知道,實際上只有三種容器組件——Map,List 和Set,而每種又有兩到三個實現。最常用的幾個容器已經用粗黑線框了起來。看到這裏,這張圖就不再那麼令人望而生畏了。
用點號框起來的是interface,用虛線框起來的是abstract 類,實線
則表示普通的(“實體concrete”)類。點線的箭頭表示類實現了這個interface(或者,abstract 類表示部分實現了這個interface)。實線
箭頭表示這個類可以製造箭頭所指的那個類的對象。比如,Collection
能製造Iterator,而List 還能製造ListIterator(也能製造Iterator,因爲List 是繼承自Collection 的)。
與存放對象有關的接口包括Collection,List,Set 和Map。在理想情況下,絕大多數代碼應該只同這些接口打交道,只是在創建容器的時候纔要精確地指明它的確切類型。所以你可以這樣創建一個List。
List x = new LinkedList( );

當然,你也可以選擇讓x 成爲LinkedList(而不是泛型的List),這樣x 就帶上了準確的類型信息interface 的優雅 (同時也是它的本意)就在於,你想修改具體的實現的時候,只要改一下創建的聲明就可以了,就
像這樣:
List x = new ArrayList( );
無需驚動其它代碼(用迭代器也能獲得一些這種泛型性)。這個類系裏面有很多以“Abstract”開頭的類,初看起來這可能會讓人有點不明白。實際上它們只是一些部分實現某個接口的辦成品。假如你要編一個你自己的Set,不要從Set 接口開始挨個實現它的方法;相反你最好繼承AbstractSet,這樣就能把編程的工作量壓縮到最低了。但是,實際上容器類庫的功能已經夠強的了,我們要求的事情它幾乎都能做
到。所以對我們來說,你完全可以忽略以“Abstract”開頭的類。

List 的功能
正如你從ArrayList 那裏所看到的,List 的基本用法是相當簡單的。雖然絕大多數時候,你只是用add( )加對象,用get( )取對象,用iterator( )獲取這個序列的Iterator,但List 還有一些別的很有用的
方法。
實際上有兩種List:擅長對元素進行隨機訪問的,較常用的ArrayList,和更強大的LinkedList。LinkedList 不是爲快速的隨機訪問而設計的,但是它卻有一組更加通用的方法。 

List (接口) List 的最重要的特徵就是有序;它會確保以一定的順序保存元素。List 在Collection 的基礎上添加了大量方法,使之能在序列中間插入和刪除元素。(只對LinkedList 推薦使用。)List 可以製造ListIterator 對象,你除了能用它在List 的中間插入和刪除元素之外,還能用它沿兩個方向遍歷List。
ArrayList* 一個用數組實現的List。能進行快速的隨機訪問, 但是往列表中間插入和刪除元素的時候比較慢。ListIterator 只能用在反向遍歷ArrayList 的場 合,不要用它來插入和刪除元素,因爲相比LinkedList,在ArrayList 裏面用ListIterator 的系統開銷比較高。
LinkedList 對順序訪問進行了優化。在List 中間插入和刪除元 素的代價也不高。隨機訪問的速度相對較慢。(用ArrayList 吧。)此外它還有addFirst( ), addLast( ),getFirst( ),getLast( ), removeFirst( )和removeLast( )等方法(這些 方法,接口和基類均未定義),你能把它當成棧(stack),隊列(queue)或雙向隊列(deque)來用。 下面這段程序把各種操作都集中到方法裏面:List 都能作的事(basicTest( )),用Iterator 在列表中移動(iterMotion( )),修改 列表的元素(iterManipulation( )),查看List 的操作結果(testVisual( )),以及LinkedList 所獨有的方法。
Set 的功能
Set 的接口就是Collection 的,所以不像那兩個List,它沒有額外的
功能。實際上Set 確確實實就是一個Collection——只不過行爲方式不
同罷了。(這是繼承和多態性的完美運用:表達不同地行爲。)Set 會拒絕
持有多個具有相同值的對象的實例(對象的“值”又是由什麼決定的呢?
這個問題比較複雜,我們以後會講的)。
Set (接口) 加入Set 的每個元素必須是唯一的;否則, Set 是不會把它加進去的。要想加進Set, Object 必須定義equals( ),這樣才能標明對象的唯一性。Set 的接口和Collection 的
一模一樣。Set 的接口不保證它會用哪種順序來存儲元素。
HashSet* 爲優化查詢速度而設計的Set。要放進HashSet 裏面的Object 還得定義hashCode( )。
TreeSet 是一個有序的Set,其底層是一棵樹。這樣你 就能從Set 裏面提取一個有序序列了。
LinkedHashSet (JDK 1.4) 一個在內部使用鏈表的Set,既有HashSet 的查詢速度,又能保存元素被加進去的順序(插入順序)。用Iterator 遍歷Set 的時候, 它是按插入順序進行訪問的。
Map 的功能
ArrayList 能讓你用數字在一個對象序列裏面進行選擇,所以從某種意義上講,它是將數字和對象關聯起來。但是,如果你想根據其他條件在一個對象序列裏面進行選擇的話,那又該怎麼做呢?從概念上講,它看上去像是一個ArrayList,但它不用數字,而是用另一個對象來查找對象!這是一種至關重要的編程技巧。這一概念在Java 中表現爲Map。put(Object key, Object value)方法會往Map 裏面加一個值,並且把這個值同鍵(你查找時所用的對象)聯繫起來。給出鍵之後,get(Object key)就會返回與之相關聯的值。
你也可以用containsKey( ) 和 containsValue( )測試Map 是否包含有某個鍵或值。
Java 標準類庫裏有好幾種Map:HashMap,TreeMap, LinkedHashMap,WeakHashMap,IdentityHashMap。
它們都實現了Map 的基本接口,但是在行爲方式方面有着明顯的差異。這些差異體現在,效率,持有和表示對象pair 的順序,持有對象的時間長短,以及如何決定鍵的相等性。性能是Map 所要面對的一個大問題。如果你知道get( )是怎麼工作的,你就會發覺(比方說)在ArrayList 裏面找對象會是相當慢的。而這
正是HashMap 的強項。它不是慢慢地一個個地找這個鍵,而是用了一種被稱爲hash code的特殊值來進行查找的。散列(hash)是一種算法,它會從目標對象當中提取一些信息,然後生成一個表示這個對象的“相對
獨特”的int。hashCode( )是Object 根類的方法,因此所有Java對象都能生成hash code。HashMap 則利用對象的hashCode( )來進行快速的查找。這樣性能就有了急劇的提高。

Map (接口) 維持鍵-值的關聯(即pairs),這樣就能用鍵來找值了。
HashMap* 基於hash表的實現。(用它來代替Hashtable。)提供時間恆定的插入與查詢。在構造函數中可以設置 hash表的capacity 和load factor。可以通過構造 函數來調節其性能。
LinkedHashMap (JDK 1.4) 很像HashMap,但是用Iterator 進行 遍歷的時候,它會按插入順序或最先使用 的順序(least-recently-used (LRU) order)進行訪問。除了用Iterator 外, 其他情況下,只是比HashMap 稍慢一 點。用Iterator 的情況下,由於是使用 鏈表來保存內部順序,因此速度會更快。
TreeMap 基於紅黑樹數據結構的實現。當你查看鍵 或pair 時,會發現它們是按順序 (根據Comparable 或Comparator,我們過 一會講)排列的。TreeMap 的特點是,你 所得到的是一個有序的Map。TreeMap 是Map 中唯一有subMap( )方法的實 現。這個方法能讓你獲取這個樹中的一部 分。
WeakHashMap 一個weak key的Map,是爲某些特殊問 題而設計的。它能讓Map 釋放其所持有的 對象。如果某個對象除了在Map 當中充當 鍵之外,在其它地方都沒有其reference 的話,那它將被當作垃圾回收。
IdentityHashMap (JDK 1.4) 一個用==,而不是equals( )來比較鍵 的hash map。不是爲我們平常使用而設 計的,是用來解決特殊問題的。 散列是往Map 裏存數據的常用算法。有時你會需要知道散列算法的工作 細節,所以我們會稍後再講。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章