上述類圖中,實線邊框的是實現類,比如ArrayList,LinkedList,HashMap等,折線邊框的是抽象類,比如AbstractCollection,AbstractList,AbstractMap等,而點線邊框的是接口,比如Collection,Iterator,List等。
發現一個特點,上述所有的集合類,都實現了Iterator接口,這是一個用於遍歷集合中元素的接口,主要包含hashNext(),next(),remove()三種方法。它的一個子接口LinkedIterator在它的基礎上又添加了三種方法,分別是add(),previous(),hasPrevious()。也就是說如果是先Iterator接口,那麼在遍歷集合中元素的時候,只能往後遍歷,被遍歷後的元素不會在遍歷到,通常無序集合實現的都是這個接口,比如HashSet,HashMap;而那些元素有序的集合,實現的一般都是LinkedIterator接口,實現這個接口的集合可以雙向遍歷,既可以通過next()訪問下一個元素,又可以通過previous()訪問前一個元素,比如ArrayList。
還有一個特點就是抽象類的使用。如果要自己實現一個集合類,去實現那些抽象的接口會非常麻煩,工作量很大。這個時候就可以使用抽象類,這些抽象類中給我們提供了許多現成的實現,我們只需要根據自己的需求重寫一些方法或者添加一些方法就可以實現自己需要的集合類,工作流昂大大降低。
ArrayList實現了List接口,是順序容器,允許放入null元素,底層通過數組實現。集合中元素被訪問的順序取決於集合的類型。如果對ArrayList進行訪問,迭代器將從索引0開始,每迭代一次,索引值加1。每個ArrayList都有一個容量(capacity),表示底層數組的實際大小,容器內存儲元素的個數不能多於當前容量。當向容器中添加元素時,如果容量不足,容器會自動增大底層數組的大小。
add()
跟C++ 的vector不同,ArrayList沒有bush_back()方法,對應的方法是add(E e),ArrayList也沒有insert()方法,對應的方法是add(int index, E e)。這兩個方法都是向容器中添加新元素,這可能會導致capacity不足,因此在添加元素之前,都需要進行剩餘空間檢查,如果需要則自動擴容。擴容操作最終是通過grow()方法完成的。
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//原來的3倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);//擴展空間並複製
}
HashMap的主幹是一個Entry數組。Entry是HashMap的基本組成單元,每一個Entry包含一個key-value鍵值對
HashMap由數組+鏈表組成的,數組是HashMap的主體,鏈表則是主要爲了解決哈希衝突而存在的,如果定位到的數組位置不含鏈表(當前entry的next指向null),那麼對於查找,添加等操作很快,僅需一次尋址即可;如果定位到的數組包含鏈表,對於添加操作,其時間複雜度爲O(n),首先遍歷鏈表,存在即覆蓋,否則新增;對於查找操作來講,仍需遍歷鏈表,然後通過key對象的equals方法逐一比對查找。所以,性能考慮,HashMap中的鏈表出現越少,性能纔會越好。
對於HashSet 而言,它是基於HashMap 實現的,HashSet底層使用HashMap 來保存所有元素,因此HashSet 的實現比較簡單,相關HashSet 的操作,基本上都是直接調用底層HashMap 的相關方法來完成。
HashSet保持數據唯一:
HashMap 的 put() 方法添加key-value 對時,當新放入 HashMap 的 Entry 中 key 與集合中原有 Entry 的 key 相同(hashCode()返回值相等,通過equals 比較也返回 true),新添加的 Entry 的 value 會將覆蓋原來 Entry 的 value(HashSet 中的 value 都是PRESENT
),但 key 不會有任何改變,因此如果向 HashSet 中添加一個已經存在的元素時,新添加的集合元素將不會被放入 HashMap中,原來的元素也不會有任何改變,這也就滿足了 Set 中元素不重複的特性。
LinkedList 和 ArrayList 一樣,都實現了 List 接口,但其內部的數據結構有本質的不同。LinkedList 是基於鏈表實現的(通過名字也能區分開來),所以它的插入和刪除操作比 ArrayList 更加高效。但也是由於其爲基於鏈表的,所以隨機訪問的效率要比 ArrayList 差。
LinkLedist實現:
1. LinkedList 是一個繼承於AbstractSequentialList的雙向鏈表。它也可以被當作堆棧、隊列或雙端隊列進行操作。
2. LinkedList 實現 List 接口,能對它進行隊列操作。
3. LinkedList 實現 Deque 接口,即能將LinkedList當作雙端隊列使用。
4. LinkedList 實現了Cloneable接口,即覆蓋了函數clone(),能克隆。
5. LinkedList 實現java.io.Serializable接口,這意味着LinkedList支持序列化,能通過序列化去傳輸。
6. LinkedList 是非同步的。
傳送門TreeMap詳解
TreeSet的底層是通過TreeMap實現的。TreeSet並不是根據插入的順序來排序,而是根據實際的值的大小來排序。TreeSet()是使用二叉樹的原理對新add()的對象按照指定的順序排序(升序、降序),每增加一個對象都會進行排序,將對象插入的二叉樹指定的位置。
Integer和String對象都可以進行默認的TreeSet排序,而自定義類的對象是不可以的,自己定義的類必須實現Comparable接口,並且覆寫相應的compareTo()函數,纔可以正常使用。
1.Hashtable是基於陳舊的Dictionary類的,HashMap是java 1.2引進的Map接口的一個實現。
2.Hashtable是線程同步的。這個類中的一些方法保證了Hashtable中的對象是線程安全的。而HashMap則是線程異步的,因此HashMap中的對象並不是線程安全的。
3.HashMap可以讓你將空值作爲一個表的條目的key或value但是Hashtable是不能放入空值的(null)。
1.Vector屬於線程安全級別的,但是大多數情況下不使用Vector,因爲線程安全需要更大的系統開銷。
2.ArrayList和Vector的擴展數組的大小不同。ArrayList在內存不夠時默認是擴展50%+ 1個,Vector是默認擴展1倍。
1.HashTable使用的對整個Map的鎖,只能支持單線程操作Map。而ConcurrentHashMap使用的是分段鎖,同時支持16個線程讀寫Map容器。
參考HashTable和ConcurrentHashMap的區別
Java中的List/Set和Map的區別:
List按對象進入的順序保存對象,不做排序和編輯操作。
Set對每個對象只接受一次,並使用自己內部的排序方法(通常,你只關心某個元素是否屬於Set而不關心它的順序--否則使用List)。
Map同樣對每個元素保存一份,但這是基於"鍵"(key)的,Map也有內置的排序,因而不關心元素添加的順序。
如果添加元素的順序對程序設計很重要,應該使用LinkedHashSet或者LinkedHashMap。
List的功能方法
實際上有兩種List:一種是基本的ArrayList其優點在於隨機訪問元素,另一種是更強大的LinkedList它並不是爲快速隨機訪問設計的,而是具有一套更通用的方法。
List:次序是List最重要的特點:它保證維護元素特定的順序。List爲Collection添加了許多方法,使得能夠向List中間插入與移除元素(這隻推薦LinkedList使用)一個List可以生成Listlterator,使用它可以從兩個方向遍歷List,也可以從List中間插入和移除元素。
ArrayList:由數組實現的List。允許對元素進行快速隨機訪問,但是向List中間插入與移除元素的速率很慢。Listlterator只應該用來由後向前遍歷ArrayList。而不是用來插入和移除元素。因爲那比LinkedList開銷要大很多。
LinkedList:對順序訪問進行了優化,向List中間插入與刪除的開銷並不大。隨機訪問則相對較慢。(使用ArrayList代替)還具有下列方法:addFirst(),addLast(),getFirst(),getLast(),removeFirst()和removeLast()這些方法(沒有在任何接口或基類中定義過)使得LinkedList可以當作堆棧、隊列和雙向隊列使用。
Set的功能方法
Set具有與Collection完全一樣的接口,因此沒有任何額外的功能,不象前面有兩個不同的List。實際上Set就是Collection,只是行爲不同。(這是繼承與多態思想的典型應用:表現不同的行爲。)Set不保存重複的元素(至於如何判斷元素相同則較爲負責)
Set:存入Set的每個元素都必須是唯一的,因爲Set不保存重複元素。加入Set的元素必需定義equals()方法以確保對象的唯一性。Set與Collection有完全一樣的接口。Set接口不保證維護元素的次序。
HashSet:爲快速查找設計的Set。存入HashSet的對象必須定義hashCode()。
TreeSet:保存次序的Set,底層爲樹結構。使用它可以從Set中提取有序的序列。
LinkedHashSet:具有HashSet的查詢速度,且內部使用鏈表維護元素的順序(插入的次序)。於是在使用迭代器遍歷Set時,結果會按元素插入的次序顯示。
Map的功能方法
方法put(Objectkey,Object value)添加一個"值"(想要得東西)和與"值"相關的"鍵"(key)(使用它來查找)。方法get(Object key)返回與給定"鍵"相關聯的"值"。可以用containsKey()和containsValue()測試Map中是否包含某個"鍵"或"值"。標準的java類庫中包含了幾種不同的Map:HashMap,TreeMap,LinkedHashMap,WeakHashMap,ldentityHashMap。它們都有同樣的基本接口Map,但是行爲、效率、排序策略、保存對象的生命週期和判定"鍵"等價的策略等各不相同。
執行效率是Map的一個大問題。看看get()要做哪些事,就會明白爲什麼在ArrayList中搜索"鍵"是相當慢的。這正是HashMap提高速度的地方。HashMap使用了特殊的值,稱爲"散列碼"(hash code),來取代對鍵的緩慢搜索。"散列碼"是"相對唯一"用以代表對象的int值,它是通過將該對象的某些信息進行轉換而生成的。所有java對象都能產生散列碼,因爲hashCode()是定義在基類Object中的方法。
HashMap就是使用對象的hashCode()進行快速查詢的。此方法能夠顯著提高性能。
Map:維護"鍵值對"的關聯性,使你可通過"鍵"查找"值"
HashMap: Map基於散列表的實現。插入和查詢"鍵值對"的開銷是固定的。可以通過構造器設置容量capacity和負載因子load factor,以調整容器的性能。
LinkedHashMap:類似於HashMap,但是迭代遍歷它時,取得"鍵值對"的順序是其插入次序,或者是最近最少使(LRU)的次序。只能HashMap慢一點。而在迭代訪問時發而更快,因爲它使用鍵表維護內部次序。
TreeMap:基於紅黑樹數據結果的實現。查看"鍵"或"鍵值對"時,它們會被排序(次序由Comparabel或Comparator決定)。TreeMap的特點在於,你得到的結果是經過排序的。TreeMap是唯一的帶有subMap()方法的Map,它可以返回一個子樹。