Java集合框架梳理(含經典面試題)

Java Collections Framework是Java提供的對集合進行定義,操做,和管理的包含一組接口,類的體系結構。html

1. 總體框架java

Java容器類庫一共有兩種主要類型:Collection和Map。層次結構以下: 程序員

藍色橢圓框爲接口類(不可實例化),黑色矩形框爲實現類或子類。面試

java.util.Collection [I]
+--java.util.List [I]
   +--java.util.ArrayList [C]
   +--java.util.LinkedList [C]
   +--java.util.Vector [C]
      +--java.util.Stack [C]
+--java.util.Set [I]
   +--java.util.HashSet [C]
   +--java.util.SortedSet [I]
      +--java.util.TreeSet [C]
 
java.util.Map [I]
+--java.util.SortedMap [I]
   +--java.util.TreeMap [C]
+--java.util.Hashtable [C]
+--java.util.HashMap [C]
+--java.util.LinkedHashMap [C]
+--java.util.WeakHashMap [C]
 
[I]:接口
[C]:類
 
2. Collection接口
Collection是最基本的集合接口,一個Collection表明一組Object的集合,這些Object被稱做Collection的元素。子類以下:
  • List:有序,元素可重複的集合。由於用戶可以使用索引來訪問List中的元素。
    • ArrayList:底層的數據結構使用的是數組,實現了可變大小的數組。非同步的(unsynchronized)。擅長隨機訪問,查詢快、插入刪除移動元素較慢。方法:size(),isEmpty(),get(),set(),add()。
    • LinkedList:底層使用的鏈表數據結構。非同步。增刪移元素快,隨機訪問(查詢)較差。提供額外的get,remove,insert方法在 LinkedList的首部或尾部。這些操做使LinkedList可被用做堆棧(stack),隊列(queue)或雙向隊列(deque)。
    • Vector:相似ArrayList,可是是同步的(synchronized),當一個Iterator被建立且正在被使用,另外一個線程改變了Vector的狀態時,調用Iterator的方法時將拋出ConcurrentModificationException,所以必須捕獲該異常。
      • Stack:繼承自Vector,實現一個後進先出的堆棧。Stack提供5個額外的方法使得Vector得以被看成堆棧使用。基本的push()和pop(),還有peek()獲得棧頂的元素,empty()測試堆棧是否爲空,search()檢測一個元素在堆棧中的位置。Stack剛建立後是空棧。
  • Set:無序,元素不可重複的集合,由於沒有索引。Set最多有一個null元素。
    • HashSet:底層數據結構是哈希表。非同步。保證元素的惟一性:hashCode和equals()。適合存取和查找。
      • LinkedHashSet:鏈表+哈希表。需維護元素的插入順序,所以性能略低於HashSet,但在迭代訪問Set裏的所有元素時(遍歷)將有很好的性能(鏈表適合進行遍歷)。
    • TreeSet:底層數據結構是二叉樹。有序,用二叉樹排序。保證元素的惟一性:compareTo()。
  • Queue:先進先出的容器。新元素插入(offer)到隊列尾部,訪問元素(poll)操做會返回隊列頭部的元素,不容許隨機訪問隊列中的元素。
    • PriorityQueue:優先級隊列,保存隊列元素的順序並非按照加入隊列的順序,而是按照隊列元素的大小進行從新排序。
 
3. Map接口
保存具備"映射關係"的數據。Map集合裏保存着兩組值,一組值用於保存Map裏的key,另一組值用於保存Map裏的value。key和value均可以是任何引用類型的數據。Map的key不容許重複。
  • HashTable:同步。不容許空。
  • HashMap:非同步。容許null,即null value和null key(最多一個)。
  • WeekHashMap:一種改進的HashMap,對key實行「弱引用」:若是一個key再也不被外部所引用,那麼該元素能夠被GC回收。
  • TreeMap:有序。
 
Map的遍歷: keySet()和entrySet()
3.1  Map.keySet()
將Map中全部的鍵存入到set集合中。由於set具有迭代器。全部能夠迭代方式取出全部的鍵,再根據get方法。獲取每個鍵對應的值。迭代後只能經過get()取key 。取到的結果會亂序,是由於取得數據行主鍵的時候,使用了HashMap.keySet()方法,而這個方法返回的Set結果,裏面的數據是亂序排放的。示例以下:
Map map = new HashMap();
map.put("key1","lisi1");
map.put("key2","lisi2");
map.put("key3","lisi3");
//Map的keySet()方法先獲取全部鍵的set集合,並經過Set的迭代器進行迭代遍歷
Iterator it = map.keySet().iterator();
while(it.hasNext()){
Object key = it.next();
//經過迭代器遍歷Map的值對象
System.out.println(map.get(key)); 
}

 

3.2 Map.entrySet()算法

Set<Map.Entry<K,V>> entrySet();  //把(key-value)做爲一個總體一對一對地存放到Set集合當中的。Map.Entry表示映射關係。entrySet():迭代後能夠e.getKey(),e.getValue()兩種方法來取key和value。返回的是Entry接口。示例:編程

Map map = new HashMap();
map.put("key1","lisi1");
map.put("key2","lisi2");
map.put("key3","lisi3");
//將map集合中的映射關係存入到Set集合,並獲取迭代器用來遍歷
Iterator it = map.entrySet().iterator();
while(it.hasNext()){
//迭代遍歷,並轉類型爲Entry
Entry e =(Entry) it.next();
//Entry.getKey ()、Entry.getValue()能夠分別獲得Entry對象的鍵和值
System.out.println("鍵"+e.getKey () + "的值爲" + e.getValue());
}

對比:推薦使用entrySet(),由於效率更高。數組

對於keySet()方法,遍歷了兩次Map,一次用來從HashMap中取出全部key到Set集合,一次從HashMap中取出全部key對應的value。而entrySet()只遍歷了一次,由於直接將key-value做爲總體放入了Entry對象中,因此效率更高。安全

 

4. 主要實現類對比數據結構

4.1 Vector和ArrayList併發

相同點:都實現了List接口,元素有序可重複;都基於數組的數據結構實現,都容許直接序號索引元素,因此隨機性較好,適合用於查詢,可是增刪移動數據比較慢(用LinkedList);

不一樣點:Vector是線程同步的,因此它也是線程安全的,而ArrayList是線程異步的,是不安全的;Vector因爲使用了synchronized方法(線程安全)因此性能上比ArrayList要差;在集合中使用數據量比較大的數據,用Vector有必定的優點。

 

4.2ArrayList和LinkedList
相同點:都實現了List接口,元素有序可重複;

不一樣點:具體實現:ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構;效率:對於隨機訪問get和set,ArrayList優於LinkedList,但對於新增和刪除操做add和remove,LinedList比較佔優點。由於LinkedList使用雙向鏈表實現存儲,按序號索引數據須要進行向前或向後遍歷,可是插入數據時只須要記錄本項的先後項便可,而ArrayList每插入一條數據,要移動插入點及以後的全部數據。 這一點要看實際狀況的。若只對單條數據插入或刪除,ArrayList的速度反而優於LinkedList。但如果批量隨機的插入刪除數據,LinkedList的速度大大優於ArrayList。 

 

4.3 HashMap與TreeMap
都實現了Map接口,是一種鍵值對的映射關係。 

HashMap經過hashcode對其內容進行快速查找,而TreeMap中全部的元素都保持着某種固定的順序,若是你須要獲得一個有序的結果你就應該使用TreeMap(HashMap中元素的排列順序是不固定的)。
在Map 中插入、刪除和定位元素,HashMap是最好的選擇。但若是您要按天然順序或自定義順序遍歷鍵,那麼TreeMap會更好。

使用HashMap要求添加的鍵類明肯定義了hashCode()和 equals()的實現。
兩個map中的元素同樣,但順序不同,致使hashCode()不同。
一樣作測試:
在HashMap中,一樣的值的map,順序不一樣,equals時,false;
而在TreeMap中,一樣的值的map,順序不一樣,equals時,true,說明,treeMap在equals()時是整理了順序了的。

 

4.4 HashTable與HashMap
都實現了Map接口,是一種鍵值對的映射關係。採用的hash/rehash算法相似,性能差別不大。

HashMap不是同步的,是線程不安全的;容許一個null鍵和多個null值。HashTable是同步的,線程安全;不容許有null的鍵或值。

HashTable有contains()方法,在HashMap中只有containsKey()和containsValue()來檢測哈希表中是否有某個key或者value,有則返回true。

 

總結:
  • 有序:實現List接口的ArrayList、LinkedList、Vector等有序,和實現了SortedXX的TreeXX有序:TreeSet、TreeMap;其餘統統無序。在除須要排序時使用TreeSet,TreeMap外,都應使用HashSet,HashMap,由於他們的效率更高。
  • 接口:Collection、List、Set、SortedSet、Queue、Map、SortedMap。共7個,接口不可被實例化。
  • 同步的類:Vector、HashTable。同步的會使得效率下降。因此若是程序在單線程環境中,或者訪問僅僅在一個線程中進行,考慮非同步的類,其效率較高,若是多個線程可能同時操做一個類,應該使用同步的類,保證安全性。
  • 若是涉及到堆棧,隊列等操做,應該考慮用List,對於須要快速插入,刪除元素,應該使用LinkedList,若是須要快速隨機訪問元素,應該使用ArrayList。  
  • 要特別注意對哈希表的操做,做爲key的對象要正確覆蓋equals()和hashCode()方法。
  • 容器類僅能持有對象引用(指向對象的指針),而不是將對象信息copy一份至數列某位置。一旦將對象置入容器內,便損失了該對象的型別信息。
  • 儘可能返回接口而非實際的類型,如返回List而非ArrayList,這樣若是之後須要將ArrayList換成LinkedList時,客戶端代碼不用改變。這就是針對抽象編程。
 
注意:
一、Collection沒有get()方法來取得某個元素。只能經過iterator()遍歷元素。
二、Set和Collection擁有如出一轍的接口。
三、List,能夠經過get()方法來一次取出一個元素。使用數字來選擇一堆對象中的一個,get(0)...。(add/get)
四、通常使用ArrayList。用LinkedList構造堆棧stack、隊列queue。
五、Map用 put(k,v) / get(k),還可使用containsKey()/containsValue()來檢查其中是否含有某個key/value。
      HashMap會利用對象的hashCode來快速找到key。
 
 
5. 面試題集合

  一、什麼是Java集合API

  Java集合框架API是用來表示和操做集合的統一框架,它包含接口、實現類、以及幫助程序員完成一些編程的算法。簡言之,API在上層完成如下幾件事:編程更加省力,提升城程序速度和代碼質量;非關聯的API提升互操做性;節省學習使用新API成本;節省設計新API的時間;鼓勵、促進軟件重用。

  具體來講,有7個集合接口,最基本的是Collection接口,由三個接口Set、List、Queue、SortedSet繼承,另外兩個接口是Map、SortedMap,這兩個接口不繼承Collection,表示映射而不是真正的集合。

 

  二、什麼是Iterator

  一些集合類(Set)提供了內容遍歷的功能,經過java.util.Iterator接口。這些接口容許遍歷對象的集合。依次操做每一個元素對象。當使用 Iterators時,在得到Iterator的時候包含一個集合快照。一般在遍歷一個Iterator的時候不建議修改集合本省。

  • Iterator與ListIterator有什麼區別?

  Iterator:只能正向遍歷集合,適用於獲取移除元素。ListIerator:繼承Iterator,能夠雙向列表的遍歷,一樣支持元素的修改。

 

  三、什麼是HaspMap和Map?

  Map是接口,Java 集合框架中一部分,用於存儲鍵值對,HashMap是用哈希算法實現Map的類。HashMap是異步的,線程不安全,容許有null鍵值。

 

HashMap基於hashing原理,咱們經過put(key,value)和get(key)方法儲存和獲取對象。當咱們將鍵值對傳遞給put()方法時,它調用鍵對象的hashCode()方法來計算hashcode,而後找到bucket位置來儲存值對象(HashMap是在bucket中儲存鍵對象和值對象,做爲Map.Entry)。當獲取對象時,經過鍵對象的equals()方法找到正確的鍵值對,而後返回值對象。HashMap使用鏈表來解決碰撞問題,當發生碰撞了,對象將會儲存在鏈表的下一個節點中。 HashMap在每一個鏈表節點中儲存鍵值對對象。

 

(1)首先equals()和hashcode()這兩個方法都是從object類中繼承過來的。

equals()是對兩個對象的地址值進行的比較(即比較引用是否相同)。

hashCode()是一個本地方法,它的實現是根據本地機器相關的。計算出對象實例的哈希碼,並返回哈希碼,又稱爲散列函數。hashCode()方法的計算依賴於對象實例的D(內存地址),故每一個Object對象的hashCode都是惟一的;固然,當對象所對應的類重寫了hashCode()方法時,結果就大相徑庭了。之因此有hashCode方法,是由於在批量的對象比較中,hashCode要比equals來得快,不少集合都用到了hashCode,好比HashTable。

(2)兩個對象,若是equals()相等,hashCode()必定相等;兩個對象,若是hashCode()相等,equals()不必定相等(Hash散列值有衝突的狀況,雖然機率很低)。

    能夠考慮在集合中,判斷兩個對象是否相等的規則是:
    第一步,若是hashCode()相等,則查看第二步,不然不相等;
    第二步,查看equals()是否相等,若是相等,則兩obj相等,不然仍是不相等。

(3)覆蓋equals()要先覆蓋hashCode(),不然在應用HashMap時會出現各類錯誤。

 

  • 當兩個不一樣的鍵對象的hashcode相同時會發生什麼? 

它們會儲存在同一個bucket位置的鏈表中。鍵對象的equals()方法用來找到鍵值對。

 

  • 怎樣使HashMap同步?

  (1)HashMap能夠經過Map map = Collections.synchronizedMap(hashMap)來達到同步的效果。

       (2)ConcurrentHashMap

    (詳細的ConcurrentHashMap源碼解析:http://www.importnew.com/22007.html

 

  四、HashMap與HashTable有什麼區別?對比Hashtable VS HashMap

  二者都是用key-value方式獲取數據。Hashtable是原始集合類之一(也稱做遺留類)。HashMap做爲新集合框架的一部分在Java2的1.2版本中加入。它們之間有一下區別:

  ● HashMap和Hashtable大體是等同的,除了非同步和空值(HashMap容許null值做爲key和value,而Hashtable不能夠)。

  ● HashMap不是同步的,而Hashtable是同步的。

       ● 由於線程安全、哈希效率的問題,HashMap效率比HashTable的要高。

       ● HashTable有contains()方法,在HashMap中只有containsKey()和containsValue()來檢測哈希表中是否有某個key或者value,有則返回true。

  ● 迭代HashMap採用快速失敗機制,而Hashtable不是,因此這是設計的考慮點。

       ● HashMap無法保證映射的順序一直不變,可是做爲HashMap的子類LinkedHashMap,若是想要預知的順序迭代(默認按照插入順序),你能夠很輕易的置換爲HashMap,若是使用Hashtable就沒那麼容易了。

 

HashTable和ConcurrentHashMap都是hash表,都是同步的,不一樣點在於:

HashTable裏使用的是synchronized關鍵字來確保同步,這實際上是對對象加鎖,鎖住的都是對象總體。意味着在一個時間點只能有一個線程能夠修改哈希表,任何線程在執行hashtable的更新操做前須要獲取對象鎖,其餘線程等待鎖的釋放。當Hashtable的大小增長到必定的時候,性能會急劇降低,由於迭代時須要被鎖定很長的時間,效率低下。

ConcurrentHashMap算是對上述問題的優化,它引入了一個「分段鎖」的概念來實現高併發。在ConcurrentHashMap中,就是把HashMap分紅了N(默認爲16)個Segment,諸如get,put,remove等經常使用操做只鎖當前須要用到的桶,根據key.hashCode()算出放到哪一個Segment中。在put的時候須要鎖住該Segment,get時候不加鎖,使用volatile來保證可見性,只有在求size等全局操做時纔可能須要鎖定整個表。

試想,原來只能一個線程進入,如今卻能同時16個寫線程進入(寫線程才須要鎖定,而讀線程幾乎不受限制),併發性的提高是顯而易見的,所以ConcurrentHashMap的效率遠高於HashTable。

 

  • 何時使用Hashtable,何時使用HashMap

  基本的不一樣點是Hashtable同步HashMap不是的,因此不管何時有多個線程訪問相同實例的可能時,就應該使用Hashtable,反之使用HashMap。非線程安全的數據結構能帶來更好的性能。若是在未來有一種可能—你須要按順序得到鍵值對的方案時,HashMap是一個很好的選擇,由於有HashMap的一個子類 LinkedHashMap。因此若是你想可預測的按順序迭代(默認按插入的順序),你能夠很方便用LinkedHashMap替換HashMap。反觀要是使用的Hashtable就沒那麼簡單了。同時若是有多個線程訪問HashMap,Collections.synchronizedMap()能夠代替,總的來講HashMap更靈活。

 

  五、什麼叫作快速失敗特性

  從高級別層次來講快速失敗是一個系統或軟件對於其故障作出的響應。一個快速失敗系統設計用來即時報告可能會致使失敗的任何故障狀況,它一般用來中止正常的操做而不是嘗試繼續作可能有缺陷的工做。當有問題發生時,快速失敗系統即時可見地發錯錯誤告警。在Java中,快速失敗與iterators有關。若是一個iterator在集合對象上建立了,其它線程欲「結構化」的修改該集合對象,併發修改異常 (ConcurrentModificationException) 拋出。

 

  六、爲何Vector類認爲是廢棄的或者是非官方地不推薦使用?或者說爲何咱們應該一直使用ArrayList而不是Vector

  你應該使用ArrayList而不是Vector是由於默認狀況下你是非同步訪問的,Vector同步了每一個方法,你幾乎從不要那樣作,一般有想要同步的是整個操做序列。同步單個的操做也不安全(若是你迭代一個Vector,你仍是要加鎖,以免其它線程在同一時刻改變集合).並且效率更慢。固然一樣有鎖的開銷即便你不須要,這是個很糟糕的方法在默認狀況下同步訪問。你能夠一直使用Collections.sychronizedList來裝飾一個集合。

  事實上Vector結合了「可變數組」的集合和同步每一個操做的實現。這是另一個設計上的缺陷。Vector還有些遺留的方法在枚舉和元素獲取的方法,這些方法不一樣於List接口,若是這些方法在代碼中程序員更趨向於想用它。儘管枚舉速度更快,可是他們不能檢查若是集合在迭代的時候修改了,這樣將致使問題。儘管以上諸多緣由,Oracle也從沒宣稱過要廢棄Vector。

 
殘留任務:HashSet、HashMap、ConcurrentHashMap等源碼理解
 
參考連接:
集合框架介紹和總結:http://blog.sina.com.cn/s/blog_a345a8960101k9vx.html
實現類對比來源:http://www.cnblogs.com/leeplogs/p/5891861.html
集合方法介紹和部分面試題:http://blog.csdn.net/zsm653983/article/details/7562324
其餘文中超連接
相關文章
相關標籤/搜索