Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
Collection接口
Collection是最基本的集合接口,一個Collection代表一組Object,即Collection的元素(Elements)。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。
讓我們轉到對框架實現的研究,具體的集合類遵循命名約定,並將基本數據結構和框架接口相結合。除了四個歷史集合類外,Java 2 框架還引入了六個集合實現,如下表所示。關於歷史集合類如何轉換、比如說,如何修改Hashtable
並結合到框架中,請參閱歷史集合類 。
接口 | 實現 | 歷史集合類 |
Set |
HashSet |
|
TreeSet |
||
List |
ArrayList |
Vector |
LinkedList |
Stack |
|
Map |
HashMap |
Hashtable |
TreeMap |
Properties |
這里沒有 Collection
接口的實現。歷史集合類,之所以這樣命名是因為從 Java 類庫 1.0 發行版就開始沿用至今了。
如果從歷史集合類轉換到新的框架類,主要差異之一在於所有的操作都和新類不同步。您可以往新類中添加同步的實現,但您不能把它從舊的類中除去。
Collection collection = new ArrayList();(這樣寫的好處在於,以后如果要理性不同的集合,可以省略很多麻煩。因為都是用Collection接口里的方法,)
boolean |
add(E o) 確保此 collection 包含指定的元素(可選操作)。 |
|
boolean |
addAll(Collection<? extends E> c) 將指定 collection 中的所有元素都添加到此 collection 中(可選操作)。 |
|
void |
clear() 移除此 collection 中的所有元素(可選操作)。 |
|
boolean |
contains(Object o) 如果此 collection 包含指定的元素,則返回 true。 |
|
boolean |
containsAll(Collection<?> c) 如果此 collection 包含指定 collection 中的所有元素,則返回true。 |
|
boolean |
equals(Object o) 比較此 collection 與指定對象是否相等。 |
|
int |
hashCode() 返回此 collection 的哈希碼值。 |
|
boolean |
isEmpty() 如果此 collection 不包含元素,則返回 true。 |
|
Iterator<E> |
iterator() 返回在此 collection 的元素上進行迭代的迭代器。 |
|
boolean |
remove(Object o) 從此 collection 中移除指定元素的單個實例,如果存在的話(可選操作)。 |
|
boolean |
removeAll(Collection<?> c) 移除此 collection 中那些也包含在指定 collection 中的所有元素(可選操作)。 |
|
boolean |
retainAll(Collection<?> c) 僅保留此 collection 中那些也包含在指定 collection 的元素(可選操作)。 |
|
int |
size() 返回此 collection 中的元素數。 |
|
Object[] |
toArray() 返回包含此 collection 中所有元素的數組。 |
|
|
toArray(T[] a) 返回包含此 collection 中所有元素的數組;返回數組的運行時類型與指定數組的運行時類型相同。 |
*All方法參數的類型都為Collection ,大多數方法都是返回boolean類型值,Collection 接口用於表示任何對象或元素組。想要盡可能以常規方式處理一組元素時,就使用這一接口。(如,可以直接add(100),可以是普通數據類型)。
容器類對象在調用remove,contains等方法時需要比較對象是否相等地,這會涉及到對象類型的equals方法和hashcode方法。即,相等的對象應該有相等的hashcode.當然,如果是自定義的類型,需要重寫這兩個方法。
iterator接口
boolean |
hasNext() 如果仍有元素可以迭代,則返回 true。 |
E |
next() 返回迭代的下一個元素。 |
void |
remove() 從迭代器指向的集合中移除迭代器返回的最后一個元素(可選操作)。 |
boolean |
add(E o) 如果 set 中尚未存在指定的元素,則添加此元素(可選操作)。 |
|
boolean |
addAll(Collection<? extends E> c) 如果 set 中沒有指定 collection 中的所有元素,則將其添加到此 set 中(可選操作)。 |
|
void |
clear() 移除 set 中的所有元素(可選操作)。 |
|
boolean |
contains(Object o) 如果 set 包含指定的元素,則返回 true。 |
|
boolean |
containsAll(Collection<?> c) 如果此 set 包含指定 collection 的所有元素,則返回 true。 |
|
boolean |
equals(Object o) 比較指定對象與此 set 的相等性。 |
|
int |
hashCode() 返回 set 的哈希碼值。 |
|
boolean |
isEmpty() 如果 set 不包含元素,則返回 true。 |
|
Iterator<E> |
iterator() 返回在此 set 中的元素上進行迭代的迭代器。 |
|
boolean |
remove(Object o) 如果 set 中存在指定的元素,則將其移除(可選操作)。 |
|
boolean |
removeAll(Collection<?> c) 移除 set 中那些包含在指定 collection 中的元素(可選操作)。 |
|
boolean |
retainAll(Collection<?> c) 僅保留 set 中那些包含在指定 collection 中的元素(可選操作)。 |
|
int |
size() 返回 set 中的元素數(其容量)。 |
|
Object[] |
toArray() 返回一個包含 set 中所有元素的數組。 |
|
|
toArray(T[] a) 返回一個包含 set 中所有元素的數組;返回數組的運行時類型是指定數組的類型。 |
按照定義,Set
接口繼承 Collection
接口,而且它不允許集合中存在重復項。所有原始方法都是現成的,沒有引入新方法。具體的Set
實現類依賴添加的對象的 equals()
方法來檢查等同性。
HashSet 類和 TreeSet 類
“集合框架”支持 Set
接口兩種普通的實現:HashSet
和TreeSet
。在更多情況下,您會使用 HashSet
存儲重復自由的集合。考慮到效率,添加到 HashSet
的對象需要采用恰當分配散列碼的方式來實現hashCode()
方法。雖然大多數系統類覆蓋了 Object
中缺省的hashCode()
實現,但創建您自己的要添加到 HashSet
的類時,別忘了覆蓋 hashCode()
。當您要從集合中以有序的方式抽取元素時,TreeSet
實現會有用處。為了能順利進行,添加到TreeSet
的元素必須是可排序的。 “集合框架”添加對 Comparable
元素的支持,在排序的“可比較的接口”部分中會詳細介紹。我們暫且假定一棵樹知道如何保持java.lang
包裝程序器類元素的有序狀態。一般說來,先把元素添加到 HashSet
,再把集合轉換為TreeSet
來進行有序遍歷會更快。
為優化 HashSet
空間的使用,您可以調優初始容量和負載因子。TreeSet
不包含調優選項,因為樹總是平衡的,保證了插入、刪除、查詢的性能為log(n)
。
HashSet
和 TreeSet
都實現 Cloneable
接口。
集的使用示例
為演示具體 Set
類的使用,下面的程序創建了一個 HashSet
,並往里添加了一組名字,其中有個名字添加了兩次。接着,程序把集中名字的列表打印出來,演示了重復的名字沒有出現。接着,程序把集作為TreeSet
來處理,並顯示有序的列表。
import java.util.*; public class SetExample { public static void main(String args[]) { Set set = new HashSet(); set.add("Bernadine"); set.add("Elizabeth"); set.add("Gene"); set.add("Elizabeth"); set.add("Clara"); System.out.println(set); Set sortedSet = new TreeSet(set); System.out.println(sortedSet); } }
運行程序產生了以下輸出。請注意重復的條目只出現了一次,列表的第二次輸出已按字母順序排序。
[Gene, Clara, Bernadine, Elizabeth] [Bernadine, Clara, Elizabeth, Gene]
List
接口繼承了 Collection
接口以定義一個允許重復項的有序集合。該接口不但能夠對列表的一部分進行處理,還添加了面向位置的操作。
有序的 collection(也稱為序列)。此接口的用戶可以對列表中每個元素的插入位置進行精確地控制。用戶可以根據元素的整數索引(在列表中的位置)訪問元素,並搜索列表中的元素。
與 set 不同,列表通常允許重復的元素。更正式地說,列表通常允許滿足 e1.equals(e2) 的元素對 e1 和e2,並且如果列表本身允許 null 元素的話,通常它們允許多個 null 元素。難免有人希望通過在用戶嘗試插入重復元素時拋出運行時異常的方法來禁止重復的列表,但我們希望這種用法越少越好。
List 接口在 iterator、add、remove、equals 和 hashCode 方法的協定上加了一些其他約定,超過了Collection 接口中指定的約定。為方便起見,這里也包括了其他繼承方法的聲明。
List 接口提供了 4 種對列表元素進行定位(索引)訪問方法。列表(像 Java 數組一樣)是基於 0 的。注意,這些操作可能在和某些實現(例如 LinkedList 類)的索引值成比例的時間內執行。因此,如果調用方不知道實現,那么在列表元素上迭代通常優於用索引遍歷列表。
List 接口提供了特殊的迭代器,稱為 ListIterator,除了允許 Iterator 接口提供的正常操作外,該迭代器還允許元素插入和替換,以及雙向訪問。還提供了一個方法來獲取從列表中指定位置開始的列表迭代器。
List 接口提供了兩種搜索指定對象的方法。從性能的觀點來看,應該小心使用這些方法。在很多實現中,它們將執行高開銷的線性搜索。
List 接口提供了兩種在列表的任意位置高效插入和移除多個元素的方法。
注意:盡管列表允許把自身作為元素包含在內,但建議要特別小心:在這樣的列表上,equals 和hashCode 方法不再是定義良好的。
某些列表實現對列表可能包含的元素有限制。例如,某些實現禁止 null 元素,而某些實現則對元素的類型有限制。試圖添加不合格的元素會拋出未經檢查的異常,通常是 NullPointerException 或ClassCastException。試圖查詢不合格的元素是否存在可能會拋出異常,也可能簡單地返回 false;某些實現會采用前一種行為,而某些則采用后者。概括地說,試圖對不合格元素執行操作時,如果完成該操作后不會導致在列表中插入不合格的元素,則該操作可能拋出一個異常,也可能成功,這取決於實現的選擇。此接口的規范中將這樣的異常標記為“可選”。
面向位置的操作包括插入某個元素或 Collection
的功能,還包括獲取、除去或更改元素的功能。在 List
中搜索元素可以從列表的頭部或尾部開始,如果找到元素,還將報告元素所在的位置。
void add(int index, Object element)
boolean addAll(int index, Collection collection)
Object get(int index)
int indexOf(Object element)
int lastIndexOf(Object element)
Object remove(int index)
Object set(int index, Object element)
List
接口不但以位置友好的方式遍歷整個列表,還能處理集合的子集:
ListIterator listIterator()
ListIterator listIterator(int startIndex)
List subList(int fromIndex, int toIndex)
處理 subList()
時,位於 fromIndex
的元素在子列表中,而位於 toIndex
的元素則不是,提醒這一點很重要。以下 for-loop
測試案例大致反映了這一點:
for (int i=fromIndex; i<toIndex; i++) { // process element at position i }
此外,我們還應該提醒的是 ― 對子列表的更改(如 add()
、remove()
和 set()
調用)對底層 List
也有影響。
boolean |
add(E o) 向列表的尾部追加指定的元素(可選操作)。 |
|
void |
add(int index, E element) 在列表的指定位置插入指定元素(可選操作)。 |
|
boolean |
addAll(Collection<? extends E> c) 追加指定 collection 中的所有元素到此列表的結尾,順序是指定 collection 的迭代器返回這些元素的順序(可選操作)。 |
|
boolean |
addAll(int index, Collection<? extends E> c) 將指定 collection 中的所有元素都插入到列表中的指定位置(可選操作)。 |
|
void |
clear() 從列表中移除所有元素(可選操作)。 |
|
boolean |
contains(Object o) 如果列表包含指定的元素,則返回 true。 |
|
boolean |
containsAll(Collection<?> c) 如果列表包含指定 collection 的所有元素,則返回 true。 |
|
boolean |
equals(Object o) 比較指定的對象與列表是否相等。 |
|
E |
get(int index) 返回列表中指定位置的元素。 |
|
int |
hashCode() 返回列表的哈希碼值。 |
|
int |
indexOf(Object o) 返回列表中首次出現指定元素的索引,如果列表不包含此元素,則返回 -1。 |
|
boolean |
isEmpty() 如果列表不包含元素,則返回 true。 |
|
Iterator<E> |
iterator() 返回以正確順序在列表的元素上進行迭代的迭代器。 |
|
int |
lastIndexOf(Object o) 返回列表中最后出現指定元素的索引,如果列表不包含此元素,則返回 -1。 |
|
ListIterator<E> |
listIterator() 返回列表中元素的列表迭代器(以正確的順序)。 |
|
ListIterator<E> |
listIterator(int index) 返回列表中元素的列表迭代器(以正確的順序),從列表的指定位置開始。 |
|
E |
remove(int index) 移除列表中指定位置的元素(可選操作)。 |
|
boolean |
remove(Object o) 移除列表中出現的首個指定元素(可選操作)。 |
|
boolean |
removeAll(Collection<?> c) 從列表中移除指定 collection 中包含的所有元素(可選操作)。 |
|
boolean |
retainAll(Collection<?> c) 僅在列表中保留指定 collection 中所包含的元素(可選操作)。 |
|
E |
set(int index, E element) 用指定元素替換列表中指定位置的元素(可選操作)。 |
|
int |
size() 返回列表中的元素數。 |
|
List<E> |
subList(int fromIndex, int toIndex) 返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之間的部分視圖。 |
|
Object[] |
toArray() 返回以正確順序包含列表中的所有元素的數組。 |
|
|
toArray(T[] a) 返回以正確順序包含列表中所有元素的數組;返回數組的運行時類型是指定數組的運行時類型。 |
其中set方法返回的是被替換的內容。
Linked 改快讀慢
Array 讀快改慢
Hash 兩都之間
Collection是集合接口
|————Set子接口:無序,不允許重復。
|————List子接口:有序,可以有重復元素。
區別:Collections是集合類
Set和List對比:
Set:檢索元素效率低下,刪除和插入效率高,插入和刪除不會引起元素位置改變。
List:和數組類似,List可以動態增長,查找元素效率高,插入刪除元素效率低,因為會引起其他元素位置改變。
Set和List具體子類:
Set
|————HashSet:以哈希表的形式存放元素,插入刪除速度很快。
List
|————ArrayList:動態數組
|————LinkedList:鏈表、隊列、堆棧。
Array和java.util.Vector
Vector是一種老的動態數組,是線程同步的,效率很低,一般不贊成使用。
Map
接口不是 Collection
接口的繼承。而是從自己的用於維護鍵-值關聯的接口層次結構入手。按定義,該接口描述了從不重復的鍵到值的映射。
我們可以把這個接口方法分成三組操作:改變、查詢和提供可選視圖。
改變操作允許您從映射中添加和除去鍵-值對。鍵和值都可以為 null
。但是,您不能把Map
作為一個鍵或值添加給自身。
Object put(Object key, Object value)返回值是被替換的值。
Object remove(Object key)
void putAll(Map mapping)
void clear()
查詢操作允許您檢查映射內容:
Object get(Object key)
boolean containsKey(Object key)
boolean containsValue(Object value)
int size()
boolean isEmpty()
最后一組方法允許您把鍵或值的組作為集合來處理。
public Set keySet()
public Collection values()
public Set entrySet()
因為映射中鍵的集合必須是唯一的,您用 Set
支持。因為映射中值的集合可能不唯一,您用Collection
支持。最后一個方法返回一個實現 Map.Entry
接口的元素 Set
。
Map.Entry 接口
Map
的 entrySet()
方法返回一個實現Map.Entry
接口的對象集合。集合中每個對象都是底層 Map
中一個特定的鍵-值對。
通過這個集合迭代,您可以獲得每一條目的鍵或值並對值進行更改。但是,如果底層 Map
在Map.Entry
接口的setValue()
方法外部被修改,此條目集就會變得無效,並導致迭代器行為未定義。
HashMap 類和 TreeMap 類
“集合框架”提供兩種常規的 Map
實現:HashMap
和TreeMap
。和所有的具體實現一樣,使用哪種實現取決於您的特定需要。在Map
中插入、刪除和定位元素,HashMap
是最好的選擇。但如果您要按順序遍歷鍵,那么TreeMap
會更好。根據集合大小,先把元素添加到 HashMap
,再把這種映射轉換成一個用於有序鍵遍歷的 TreeMap
可能更快。使用HashMap
要求添加的鍵類明確定義了 hashCode()
實現。有了TreeMap
實現,添加到映射的元素一定是可排序的。我們將在排序中詳細介紹。
為了優化 HashMap
空間的使用,您可以調優初始容量和負載因子。這個TreeMap
沒有調優選項,因為該樹總處於平衡狀態。
HashMap
和 TreeMap
都實現Cloneable
接口。
Hashtable
類和 Properties
類是Map
接口的歷史實現。我們將在Dictionary 類、Hashtable 類和 Properties 類中討論。
映射的使用示例
以下程序演示了具體 Map
類的使用。該程序對自命令行傳遞的詞進行頻率計數。HashMap
起初用於數據存儲。后來,映射被轉換為TreeMap
以顯示有序的鍵列列表。
import java.util.*; public class MapExample { public static void main(String args[]) { Map map = new HashMap(); Integer ONE = new Integer(1); for (int i=0, n=args.length; i<n; i++) { String key = args[i]; Integer frequency = (Integer)map.get(key); if (frequency == null) { frequency = ONE; } else { int value = frequency.intValue(); frequency = new Integer(value + 1); } map.put(key, frequency); } System.out.println(map); Map sortedMap = new TreeMap(map); System.out.println(sortedMap); } }
用 Bill of Rights 的第三篇文章的文本運行程序產生下列輸出,請注意有序輸出看起來多么有用!
無序輸出:
{prescribed=1, a=1, time=2, any=1, no=1, shall=1, nor=1, peace=1, owner=1, soldier=1, to=1, the=2, law=1, but=1, manner=1, without=1, house=1, in=4, by=1, consent=1, war=1, quartered=1, be=2, of=3}
有序輸出:
{a=1, any=1, be=2, but=1, by=1, consent=1, house=1, in=4, law=1, manner=1, no=1, nor=1, of=3, owner=1, peace=1, prescribed=1, quartered=1, shall=1, soldier=1, the=2, time=2, to=1, war=1, without=1}
Java集合框架是最常被問到的Java面試問題,要理解Java技術強大特性就有必要掌握集合框架。這里有一些實用問題,常在核心Java面試中問到。
1、什么是Java集合API
Java集合框架API是用來表示和操作集合的統一框架,它包含接口、實現類、以及幫助程序員完成一些編程的算法。簡言之,API在上層完成以下幾件事:
● 編程更加省力,提高城程序速度和代碼質量
● 非關聯的API提高互操作性
● 節省學習使用新API成本
● 節省設計新API的時間
● 鼓勵、促進軟件重用
具體來說,有6個集合接口,最基本的是Collection接口,由三個接口Set、List、SortedSet繼承,另外兩個接口是Map、SortedMap,這兩個接口不繼承Collection,表示映射而不是真正的集合。
2、什么是Iterator
一些集合類提供了內容遍歷的功能,通過java.util.Iterator接口。這些接口允許遍歷對象的集合。依次操作每個元素對象。當使用 Iterators時,在獲得Iterator的時候包含一個集合快照。通常在遍歷一個Iterator的時候不建議修改集合本省。
3、Iterator與ListIterator有什么區別?
Iterator:只能正向遍歷集合,適用於獲取移除元素。ListIerator:繼承Iterator,可以雙向列表的遍歷,同樣支持元素的修改。
4、什么是HaspMap和Map?
Map是接口,Java 集合框架中一部分,用於存儲鍵值對,HashMap是用哈希算法實現Map的類。
5、HashMap與HashTable有什么區別?對比Hashtable VS HashMap
兩者都是用key-value方式獲取數據。Hashtable是原始集合類之一(也稱作遺留類)。HashMap作為新集合框架的一部分在Java2的1.2版本中加入。它們之間有一下區別:
● HashMap和Hashtable大致是等同的,除了非同步和空值(HashMap允許null值作為key和value,而Hashtable不可以)。
● HashMap沒法保證映射的順序一直不變,但是作為HashMap的子類LinkedHashMap,如果想要預知的順序迭代(默認按照插入順序),你可以很輕易的置換為HashMap,如果使用Hashtable就沒那么容易了。
● HashMap不是同步的,而Hashtable是同步的。
● 迭代HashMap采用快速失敗機制,而Hashtable不是,所以這是設計的考慮點。
6、在Hashtable上下文中同步是什么意思?
同步意味着在一個時間點只能有一個線程可以修改哈希表,任何線程在執行hashtable的更新操作前需要獲取對象鎖,其他線程等待鎖的釋放。
7、什么叫做快速失敗特性
從高級別層次來說快速失敗是一個系統或軟件對於其故障做出的響應。一個快速失敗系統設計用來即時報告可能會導致失敗的任何故障情況,它通常用來停止正常的操作而不是嘗試繼續做可能有缺陷的工作。當有問題發生時,快速失敗系統即時可見地發錯錯誤告警。在Java中,快速失敗與iterators有關。如果一個iterator在集合對象上創建了,其它線程欲“結構化”的修改該集合對象,並發修改異常 (ConcurrentModificationException) 拋出。
8、怎樣使Hashmap同步?
HashMap可以通過Map m = Collections.synchronizedMap(hashMap)來達到同步的效果。
9、什么時候使用Hashtable,什么時候使用HashMap
基本的不同點是Hashtable同步HashMap不是的,所以無論什么時候有多個線程訪問相同實例的可能時,就應該使用Hashtable,反之使用HashMap。非線程安全的數據結構能帶來更好的性能。
如果在將來有一種可能—你需要按順序獲得鍵值對的方案時,HashMap是一個很好的選擇,因為有HashMap的一個子類 LinkedHashMap。所以如果你想可預測的按順序迭代(默認按插入的順序),你可以很方便用LinkedHashMap替換HashMap。反觀要是使用的Hashtable就沒那么簡單了。同時如果有多個線程訪問HashMap,Collections.synchronizedMap()可以代替,總的來說HashMap更靈活。
10、為什么Vector類認為是廢棄的或者是非官方地不推薦使用?或者說為什么我們應該一直使用ArrayList而不是Vector
你應該使用ArrayList而不是Vector是因為默認情況下你是非同步訪問的,Vector同步了每個方法,你幾乎從不要那樣做,通常有想要同步的是整個操作序列。同步單個的操作也不安全(如果你迭代一個Vector,你還是要加鎖,以避免其它線程在同一時刻改變集合).而且效率更慢。當然同樣有鎖的開銷即使你不需要,這是個很糟糕的方法在默認情況下同步訪問。你可以一直使用Collections.sychronizedList來裝飾一個集合。
事實上Vector結合了“可變數組”的集合和同步每個操作的實現。這是另外一個設計上的缺陷。Vector還有些遺留的方法在枚舉和元素獲取的方法,這些方法不同於List接口,如果這些方法在代碼中程序員更趨向於想用它。盡管枚舉速度更快,但是他們不能檢查如果集合在迭代的時候修改了,這樣將導致問題。盡管以上諸多原因,oracle也從沒宣稱過要廢棄Vector。