前百度面試官整理的——Java後端面試題(一)

clipboard.png

List 和 Set 的區別面試

List , Set 都是繼承自 Collection 接口 List 特色:元素有放入順序,元素可重複 ,數組

Set 特色:元素無放入順序,元素不可重複,重複元素會覆蓋掉,(元素雖然無放入順序,可是元素在set中的位 置是有該元素的 HashCode 決定的,其位置實際上是固定的,加入Set 的 Object 必須定義 equals ()方法 ,另外list 支持for循環,也就是經過下標來遍歷,也能夠用迭代器,可是set只能用迭代,由於他無序,沒法用下標來取得想 要的值。) Set和List對比 Set:檢索元素效率低下,刪除和插入效率高,插入和刪除不會引發元素位置改變。安全

List:和數組相似,List能夠動態增加,查找元素效率高,插入刪除元素效率低,由於會引發其餘元素位置改變性能優化

HashSet 是如何保證不重複的多線程

向 HashSet 中 add ()元素時,判斷元素是否存在的依據,不只要比較hash值,同時還要結合 equles 方法比較。 HashSet 中的 add ()方法會使用 HashMap 的 add ()方法。如下是 HashSet 部分源碼:架構

clipboard.png

HashMap 的 key 是惟一的,由上面的代碼能夠看出 HashSet 添加進去的值就是做爲 HashMap 的key。因此不會 重複( HashMap 比較key是否相等是先比較 hashcode 在比較 equals )。併發

HashMap 是線程安全的嗎,爲何不是線程安全的(最好畫圖說明多線程 環境下不安全)?分佈式

不是線程安全的;函數

若是有兩個線程A和B,都進行插入數據,恰好這兩條不一樣的數據通過哈希計算後獲得的哈希碼是同樣的,且該位 置尚未其餘的數據。因此這兩個線程都會進入我在上面標記爲1的代碼中。假設一種狀況,線程A經過if判斷,該 位置沒有哈希衝突,進入了if語句,尚未進行數據插入,這時候 CPU 就把資源讓給了線程B,線程A停在了if語句 裏面,線程B判斷該位置沒有哈希衝突(線程A的數據還沒插入),也進入了if語句,線程B執行完後,輪到線程A執 行,如今線程A直接在該位置插入而不用再判斷。這時候,你會發現線程A把線程B插入的數據給覆蓋了。發生了線 程不安全狀況。原本在 HashMap 中,發生哈希衝突是能夠用鏈表法或者紅黑樹來解決的,可是在多線程中,可能 就直接給覆蓋了。微服務

上面所說的是一個圖來解釋可能更加直觀。以下面所示,兩個線程在同一個位置添加數據,後面添加的數據就覆蓋 住了前面添加的。

clipboard.png

若是上述插入是插入到鏈表上,如兩個線程都在遍歷到最後一個節點,都要在最後添加一個數據,那麼後面添加數 據的線程就會把前面添加的數據給覆蓋住。則

clipboard.png

在擴容的時候也可能會致使數據不一致,由於擴容是從一個數組拷貝到另一個數組。

HashMap 的擴容過程

當向容器添加元素的時候,會判斷當前容器的元素個數,若是大於等於閾值(知道這個閾字怎麼念嗎?不念 fa 值, 念 yu 值四聲)---即當前數組的長度乘以加載因子的值的時候,就要自動擴容啦。

擴容( resize )就是從新計算容量,向 HashMap 對象裏不停的添加元素,而 HashMap 對象內部的數組沒法裝載更 多的元素時,對象就須要擴大數組的長度,以便能裝入更多的元素。固然 Java 裏的數組是沒法自動擴容的,方法 是使用一個新的數組代替已有的容量小的數組,就像咱們用一個小桶裝水,若是想裝更多的水,就得換大水桶。

HashMap hashMap=new HashMap(cap);
cap =3, hashMap 的容量爲4;

cap =4, hashMap 的容量爲4;

cap =5, hashMap 的容量爲8;

cap =9, hashMap 的容量爲16;

若是 cap 是2的n次方,則容量爲 cap ,不然爲大於 cap 的第一個2的n次方的數。

HashMap 1.7 與 1.8 的 區別,說明 1.8 作了哪些優化,如何優化的?

HashMap結構圖

clipboard.png

在 JDK1.7 及以前的版本中, HashMap 又叫散列鏈表:基於一個數組以及多個鏈表的實現,hash值衝突的時候, 就將對應節點以鏈表的形式存儲。

JDK1.8 中,當同一個hash值( Table 上元素)的鏈表節點數不小於8時,將再也不以單鏈表的形式存儲了,會被 調整成一顆紅黑樹。這就是 JDK7 與 JDK8 中 HashMap 實現的最大區別。

其下基於 JDK1.7.0_80 與 JDK1.8.0_66 作的分析

JDK1.7中

使用一個 Entry 數組來存儲數據,用key的 hashcode 取模來決定key會被放到數組裏的位置,若是 hashcode 相 同,或者 hashcode 取模後的結果相同( hash collision ),那麼這些 key 會被定位到 Entry 數組的同一個 格子裏,這些 key 會造成一個鏈表。

在 hashcode 特別差的狀況下,比方說全部key的 hashcode 都相同,這個鏈表可能會很長,那麼 put/get 操做 均可能須要遍歷這個鏈表,也就是說時間複雜度在最差狀況下會退化到 O(n)

JDK1.8中

使用一個 Node 數組來存儲數據,但這個 Node 多是鏈表結構,也多是紅黑樹結構

  • 若是插入的 key 的 hashcode 相同,那麼這些key也會被定位到 Node 數組的同一個格子裏。
  • 若是同一個格子裏的key不超過8個,使用鏈表結構存儲。
  • 若是超過了8個,那麼會調用 treeifyBin 函數,將鏈表轉換爲紅黑樹。

那麼即便 hashcode 徹底相同,因爲紅黑樹的特色,查找某個特定元素,也只須要O(log n)的開銷

也就是說put/get的操做的時間複雜度最差只有 O(log n) 聽起來挺不錯,可是真正想要利用 JDK1.8 的好處,有一個限制: key的對象,必須正確的實現了 Compare 接口 若是沒有實現 Compare 接口,或者實現得不正確(比方說全部 Compare 方法都返回0) 那 JDK1.8 的 HashMap 其實仍是慢於 JDK1.7 的

簡單的測試數據以下:

向 HashMap 中 put/get 1w 條 hashcode 相同的對象 JDK1.7: put 0.26s , get 0.55s JDK1.8 (未實現 Compare 接口): put 0.92s , get 2.1s 可是若是正確的實現了 Compare 接口,那麼 JDK1.8 中的 HashMap 的性能有巨大提高,此次 put/get 100W條 hashcode 相同的對象 JDK1.8 (正確實現 Compare 接口,): put/get 大概開銷都在320 ms 左右

final finally finalize final

  • 能夠修飾類、變量、方法,修飾類表示該類不能被繼承、修飾方法表示該方法不能被重寫、修飾變量表 示該變量是一個常量不能被從新賦值。
  • finally通常做用在try-catch代碼塊中,在處理異常的時候,一般咱們將必定要執行的代碼方法finally代碼塊 中,表示不論是否出現異常,該代碼塊都會執行,通常用來存放一些關閉資源的代碼。
  • finalize是一個方法,屬於Object類的一個方法,而Object類是全部類的父類,該方法通常由垃圾回收器來調 用,當咱們調用 System.gc() 方法的時候,由垃圾回收器調用finalize(),回收垃圾,一個對象是否可回收的 最後判斷。

感謝你耐心看完了文章…

關注做者,我會不按期在思否分享Java,Spring,MyBatis,Redis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構,BATJ面試 等資料…

相關文章
相關標籤/搜索