1. 程式人生 > >前百度面試官整理的——Java後端面試題(一)

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

 

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 部分原始碼:

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 中,發生雜湊衝突是可以用連結串列法或者紅黑樹來解決的,但是在多執行緒中,可能 就直接給覆蓋了。

上面所說的是一個圖來解釋可能更加直觀。如下面所示,兩個執行緒在同一個位置新增資料,後面新增的資料就覆蓋 住了前面新增的。

如果上述插入是插入到連結串列上,如兩個執行緒都在遍歷到最後一個節點,都要在最後新增一個數據,那麼後面新增數 據的執行緒就會把前面新增的資料給覆蓋住。則

在擴容的時候也可能會導致資料不一致,因為擴容是從一個數組拷貝到另外一個數組。

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結構圖

在 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效能優化、分散式架構,BA