遍歷 vs. 查找
遍歷和修改似乎是一對矛盾,一個可以高效率插入刪除元素的數據結構通常遍歷的性能并不是最優。于是 JCF在這里根據用戶的目標實現了兩種定制的數據結構:
哈希表(包括HashSet和HashMap)和平衡二叉樹(包括TreeSet和 TreeMap)。
由于可排序性是一種獨特的要求,所以引入了SortedSet和SortedMap,它們分別是AbstractSet和 AbstractMap的子接口,而TreeSet和TreeMap又分別是他們的一種實現。熟悉數據結構的人可能比較了解,哈希表在進行插入、刪除、查 找這樣的操作是很快的,其時間復雜度是常數級O(1);平衡二叉樹雖然插入、刪除操作比較麻煩(需要O(log n)的代價),但進行遍歷和排序卻很快。選擇完全在于用戶的側重點,但由于類型轉換的方便性,通常我們用哈希表構造一個集合以后,再把它轉換成相應的樹集 進行遍歷,以獲得較好的效果。
歷史實現 vs. 新實現
歷史實現(Legacy
Implementations)是JCF的一個術語,準確的意義不是很清楚,但大致可以認為在Java 2(JDK
1.2)出現以前的老版本中JCF的一個雛形框架。在Java
2以后,JCF才開始完善健壯起來,新實現中出現了一些新的類用于替代老版本中的成員,但由于種種原因,老版本中很多類都代表了傳統數據結構的精髓部分,
以及一些安全原因,所以仍然被我們使用著。
Enumeration vs. Iterator
Enumeration是一個傳統的集合遍歷工具,在新的JCF中使用的是Iterator,Iterator同樣具有遍歷功能,還包含一個remove()方法來刪除當前得到的元素。
Dictionary vs. Map
Dictionary
是一個現在已經被標記為deprecated的類,實現了老版本中的映射功能,現在已經完全被Map取代。它們的區別是:Dictionary中key和
value不能為null,但Map卻允許空的關鍵字和值,這一點直接影響到它們的后代:Hashtable和HashMap。
Vector vs. ArrayList
Vector
和ArrayList是數組在JCF中的體現,還記得前面講過的數組的缺點么?Vector和ArrayList就是一種可以動態增長的數組。
Vector是歷史實現,它和ArrayList的主要區別在于,Vector是同步集合(或者說是線程安全的),但ArrayList并不是同步的,由
于同步需要花一定的代價,所以ArrayList看起來要比Vector的存取訪問效率更高。關于同步我們下面還將要談到。
Hashtable vs. HashMap
Hashtable
是Dictionary的子類,屬于歷史實現,而HashMap是Map的子類,是新實現。它們的區別除了上面所說的key和value是否可以為空之
外,也有同步的差別,Hashtable是同步的,但HashMap不是。HashMap的一個經典的例子就是JSP的內置對象session。不過不要
因為Hashtable是“老前輩”而瞧不起它哦,它的一個著名的子類Properties我們可是經常會用到的。
同步 vs. 不同步
從上面的描述中我們似乎可以得出這么一個印象:歷史實現好像都是同步的,但新實現中卻 沒有。需要同步操作的理由是,可能存在多個線程對同一個集合進行操作的情況:譬如一個線程正在對某集合進行遍歷,但與此同時,另一個線程又在對該集合進行 插入或刪除,那么第一個線程的遍歷結果將是不可預測的,對于同步集合,它將會拋出一個ConcurrentModificationException異常,JCF把這種機制成為“fail-fast”。我們對比一下Vector和ArrayList的源代碼就可以發現Vector的很多方法都是有synchronized關鍵字修飾的,但ArrayList沒有。
容易遺忘的工具:Collections和Arrays
在圖1中右下角落里有兩個類叫做Collections(注意,不是
Collection!)和Arrays,這是JCF里面功能強大的工具,但初學者往往會忽視。按JCF文檔的說法,這兩個類提供了封裝器實現
(Wrapper Implementations)、數據結構算法和數組相關的應用。
想必大家不會忘記上面談到的“折半查找”、“排序”等經典算法吧,Collections類提供了豐富的靜態方法幫助我們輕松完成這些在數據結構課上煩人的工作:
binarySearch:折半查找。
sort:排序,這里是一種類似于快速排序的方法,效率仍然是O(n * log n),但卻是一種穩定的排序方法。
reverse:將線性表進行逆序操作,這個可是從前數據結構的經典考題哦!
rotate:以某個元素為軸心將線性表“旋轉”——哇,這個功能太酷了!
swap:交換一個線性表中兩個元素的位置。
……
Collections還有一個重要功能就是“封裝器”(Wrapper),它提供了一些方法可以把一個集合轉換成一個特殊的集合:
unmodifiableXXX:轉換成只讀集合,這里XXX代表六種基本集合接口:Collection、List、Map、Set、SortedMap和SortedSet。如果你對只讀集合進行插入刪除操作,將會拋出UnsupportedOperationException異常。
synchronizedXXX:轉換成同步集合。
singleton:創建一個僅有一個元素的集合,這里singleton生成的是單元素Set,singletonList和singletonMap分別生成單元素的List和Map。
空集:由Collections的靜態屬性EMPTY_SET、EMPTY_LIST和EMPTY_MAP表示。
此外,我們知道把集合轉換成對象數組可以用Collection的toArray()方法,我們也可以方便地把一個對象數組轉換成一個線性表(可不要告訴我你是一個一個地add哦):Arrays.asList()。
泛型
目前我們了解的JCF的一個重要特征是:所有加入到集合當中的對象都將在表面上失去它 們自己的特性,而看上去僅僅只是一個Object對象而已,除非你把它強制類型轉換成它們原來的對象。這一點很自然,集合嘛,對象的容器,它容納的是各種 各樣的對象,而不僅僅是某種特定類型的對象。J2SE 5.0出現以后,JCF開始引入泛型的特性,譬如我們經常碰到這樣的應用,就是把集合轉換成特定的數組,雖然Collection有toArray()的 方法,但可惜的是,這個數組的所有元素都是Object類型的,我們通常的做法是用一個for循環把數組的每個元素都進行強制類型轉換,雖然可行,但看上 去很笨拙,如果有了泛型,我們就可以預先指定要得到的類型,然后一次toArray就可以得到我們期望的數組,里面的元素全部都是指定類型了。慚愧的是, 我對5.0還不是太了解,具體可以參考J2SE 5.0的JCF文檔。