Java集合框架面試題總結

前段時間已經總結了有關Java併發的面試題:
Java併發基礎面試題總結
Java併發進階面試題總結
今天來學習一下有關Java集合的常見面試題。java

  • 說說常見的集合有哪些吧
  • 說說List,Set,Map三者的區別
  • Arraylist與LinkedList 區別
  • 爲何要用Arraylist取代Vector呢
  • 說一說ArrayList的擴容機制
  • HashMap和Hashtable的區別
  • HashMap的長度爲何是2的冪次方
  • HashMap的擴容操做是怎麼實現的
  • HashMap在JDK1.7和JDK1.8中有哪些不一樣
  • ConcurrentHashMap和Hashtable的區別
  • ConcurrentHashMap的具體實現知道嗎
  • HashSet是如何保證數據不可重複的
  • CopyOnWriteArrayList爲何併發安全且性能比Vector好
  • CopyOnWriteArrayList讀取效率爲何那麼高

說說常見的集合有哪些吧

Map接口和Collection接口是全部集合框架的父接口git

  1. Collection接口的子接口包括:Set接口和List接口
  2. Map接口的實現類主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap等
  3. Set接口的實現類主要有:HashSet、TreeSet、LinkedHashSet等
  4. List接口的實現類主要有:ArrayList、LinkedList、Stack以及Vector等

說說List,Set,Map三者的區別

  • ListList接口存儲一組不惟一(能夠有多個元素引用相同的對象),有序的對象
  • Set:不容許重複的集合。不會有多個元素引用相同的對象。
  • Map:使用鍵值對存儲。Map會維護與Key有關聯的值。兩個Key能夠引用相同的對象,但Key不能重複,典型的Key是String類型,但也能夠是任何對象。

Arraylist與LinkedList 區別

  • ArrayList是實現了基於動態數組的數據結構,LinkedList是基於鏈表的數據結構
  • 對於隨機訪問get和set,ArryList要優於LinkedList,由於LinkedList要移動指針
  • 對於新增和刪除操做add和remove,linkedList比較佔優點,由於ArrayList要移動數據。這一點要看實際狀況,若是隻對單條數據插入或刪除,ArrayList的速度反而優於LinkedList。但批量隨機插入,則考慮LinkedList。由於ArrayList每插入一條數據,要移動插入點及以後的全部數據。

爲何要用Arraylist取代Vector呢

  • Vector類的全部方法都是同步的。能夠由兩個線程安全地訪問一個Vector對象、可是一個線程訪問Vector的話代碼要在同步操做上耗費大量的時間。
  • Arraylist不是同步的,因此在不須要保證線程安全時建議使用Arraylist。

說一說ArrayList的擴容機制

詳情請看:深刻剖析ArrayList源碼github

HashMap和Hashtable的區別

  1. 線程是否安全:HashMap 是非線程安全的,HashTable 是線程安全的;HashTable 內部的方法基本都通過synchronized修飾。(若是你要保證線程安全的話就使用 ConcurrentHashMap 吧!);
  2. 效率:由於線程安全的問題,HashMap 要比 HashTable 效率高一點。另外,HashTable 基本被淘汰,不要在代碼中使用它;
  3. 對Null key 和Null value的支持:HashMap 中,null 能夠做爲鍵,這樣的鍵只有一個,能夠有一個或多個鍵所對應的值爲 null。。可是在 HashTable 中 put 進的鍵值只要有一個 null,直接拋出 NullPointerException。
  4. 初始容量大小和每次擴充容量大小的不一樣 :①建立時若是不指定容量初始值,Hashtable 默認的初始大小爲11,以後每次擴充,容量變爲原來的2n+1HashMap 默認的初始化大小爲16。以後每次擴充,容量變爲原來的2倍。②建立時若是給定了容量初始值,那麼 Hashtable 會直接使用你給定的大小,而 HashMap 會將其擴充爲2的冪次方大小(HashMap 中的tableSizeFor()方法保證)。也就是說 HashMap 老是使用2的冪做爲哈希表的大小
  5. 底層數據結構:JDK1.8 之後的 HashMap 在解決哈希衝突時有了較大的變化,當鏈表長度大於閾值(默認爲8)時,將鏈表轉化爲紅黑樹,以減小搜索時間。Hashtable 沒有這樣的機制。

HashMap的長度爲何是2的冪次方

主要是爲了 提升運算效率咱們使用的是2次冪的擴展(指長度擴爲原來2倍),因此,擴容後元素的位置要麼是在原位置,要麼是在原位置再移動2次冪的位置所以,咱們在擴充HashMap的時候,不須要像JDK1.7的實現那樣從新計算hash,只須要看看原來的hash值 新增的那個bit是1仍是0就行了,是0的話索引沒變,是1的話索引變成「原索引+oldCap

image

HashMap的擴容操做是怎麼實現的

有關 HashMap的擴容操做請移步:深刻剖析HashMap源碼面試

HashMap在JDK1.7和JDK1.8中有哪些不一樣

1.PNG

ConcurrentHashMap和Hashtable的區別?

ConcurrentHashMap和Hashtable的區別主要體如今實現線程安全的方式上不一樣。segmentfault

  • 底層數據結構:JDK1.7的 ConcurrentHashMap 底層採用分段的數組+鏈表實現,JDK1.8 採用的數據結構跟HashMap1.8的結構同樣,數組+鏈表/紅黑二叉樹。Hashtable 和 JDK1.8 以前的 HashMap 的底層數據結構相似都是採用數組+鏈表的形式。
  • 實現線程安全的方式(重要):在JDK1.7的時候,ConcurrentHashMap(分段鎖)對整個桶數組進行了分割分段(Segment),每一把鎖只鎖容器其中一部分數據,多線程訪問容器裏不一樣數據段的數據,就不會存在鎖競爭,提升併發訪問率。到了 JDK1.8 的時候已經摒棄了Segment的概念,而是直接用 Node 數組+鏈表+紅黑樹的數據結構來實現,併發控制使用 synchronized 和 CAS 來操做。(JDK1.6之後 對 synchronized鎖作了不少優化)整個看起來就像是優化過且線程安全的 HashMap,雖然在JDK1.8中還能看到 Segment 的數據結構,可是已經簡化了屬性,只是爲了兼容舊版本;②Hashtable(同一把鎖):使用 synchronized 來保證線程安全,效率很是低下。當一個線程訪問同步方法時,其餘線程也訪問同步方法,可能會進入阻塞或輪詢狀態,如使用 put 添加元素,另外一個線程不能使用 put 添加元素,也不能使用 get,競爭會愈來愈激烈效率越低。

ConcurrentHashMap底層實現

在JDK1.7中,ConcurrentHashMap採用Segment + HashEntry的方式進行實現,結構以下:數組

image

  • 該類包含兩個靜態內部類 HashEntry 和 Segment ;前者用來封裝映射表的鍵值對,後者用來充當鎖的角色;
  • Segment 是一種可重入的鎖 ReentrantLock,每一個 Segment 守護一個HashEntry 數組裏得元素,當對 HashEntry 數組的數據進行修改時,必須首先得到對應的 Segment 鎖。

在JDK1.8中,放棄了Segment臃腫的設計,取而代之的是採用Node + CAS + Synchronized來保證併發安全進行實現,結構以下:安全

image

HashSet是如何保證數據不可重複的

HashSet的底層其實就是HashMap,只不過咱們HashSet是實現了Set接口而且把數據做爲K值,而V值一直使用一個相同的虛值來保存,咱們能夠看到源碼:數據結構

public boolean add(E e) {
    return map.put(e, PRESENT)==null;// 調用HashMap的put方法,PRESENT是一個至始至終都相同的虛值
}

因爲HashMap的K值自己就不容許重複,而且在HashMap中若是K/V相同時,會用新的V覆蓋掉舊的V,而後返回舊的V,那麼在HashSet中執行這一句話始終會返回一個false,致使插入失敗,這樣就保證了數據的不可重複性。多線程

CopyOnWriteArrayList爲何併發安全且性能比Vector好

Vector是增刪改查方法都加了synchronized,保證同步,可是每一個方法執行的時候都要去得到鎖,性能就會大大降低,而CopyOnWriteArrayList 只是在增刪改上加鎖,可是讀不加鎖,在讀方面的性能就好於Vector,CopyOnWriteArrayList支持讀多寫少的併發狀況。併發

源碼參考:一文解讀CopyOnWriteArrayList

CopyOnWriteArrayList讀取效率爲何那麼高

CopyOnWriteArrayList類的全部可變操做(add,set等等)都是經過建立底層數組的新副本來實現的。當 List 須要被修改的時候,並不直接修改原有數組對象,而是對原有數據進行一次拷貝,將修改的內容寫入副本中。寫完以後,再將修改完的副本替換成原來的數據,這樣就能夠保證寫操做不會影響讀操做了。

CopyOnWriteArrayList 的名字能夠看出,CopyOnWriteArrayList 是知足 CopyOnWrite 的 ArrayList,所謂 CopyOnWrite 的意思:就是對一塊內存進行修改時,不直接在原有內存塊中進行寫操做,而是將內存拷貝一份,在新的內存中進行寫操做,寫完以後,再將原來指向的內存指針指到新的內存,原來的內存就能夠被回收。

參考

Java集合框架面試總結

相關文章
相關標籤/搜索