Java基礎篇:
題記:本系列文章,會盡量模擬面試現場對話情景, 用口語而非書面語 ,采用問答形式來展現。另外每一個問題都附上“延伸”,這部分內容是幫助小伙伴們更深的理解一些底層細節的補充,在面試中可能很少直接涉及,權當是提高自身水平的知識儲備吧。
第一部分:java容器相關
1.問:List 和 Set 都有什么區別?
分析:這種問題面試官一般想考察的都是你對這兩種數據結構的了解,已經使用時候的選擇依據,所以可以從數據結構和一些使用案例入手分別做個介紹
答:List,列表,元素可重復。常用的實現ArrayList和LinkedList,前者是數組方式來實現,后者是通過鏈表來實現,在使用選擇的時候,一般考慮的是基本數據結構的特性,比如,數組讀取效率較高,鏈表插入時效率較高。
Set,集合,特點就是存儲的元素不可重復。常用是實現,是HashSet和TreeSet,分開來談:
HashSet,底層實現是HashMap,存儲時,把值存在key,而value統一存儲一個object對象。排重的時候,是先通過對象的hashcode來判斷,如果不相等,直接存儲。如果相等,會再對key做equals判斷,如果依然相等,不存儲,如果不相等,則存入,我們知道,HashMap是數組+鏈表的基本結構,同樣的,在HashSet中,也是通過同樣的策略,存儲在相同的數組位置下的鏈表中。
TreeSet,存入自定義對象時,對象需要實現Comparable接口,重寫排序規則。使用場景一般是需要保證存儲數據的有序和唯一性。底層數據結構是自平衡的二叉排序樹(紅黑樹)
延伸:
a.在說到HashMap時,可能會直接引到HashMap相關話題,我發現這個問題面試官非常喜歡問,可能是因為HashMap可聊的較多,也能很好的考驗下應聘者對底層實現細節、源碼閱讀、刨根問底的態度。
b.涉及到二叉樹了,小端就遇到過一次問紅黑樹特性的,因為之前准備過,胸有成竹的啪啪啪正要一一道來呢,結果剛說到第二個特性,面試官就問:紅黑樹和普通的平衡二叉樹有什么區別?當時一臉懵逼樣...回來后趕緊補足,核心區別就是:紅黑樹也是二叉查找樹的一種,二叉樹需要通過自旋、或其他方式(比如紅黑樹還能通過變色)來保證平衡(否則就成了鏈表結構了,沒有時間復雜度上的優勢了),紅黑樹限定的條件相對來說比較寬松,也就是說在平衡的過程中,消耗相對較小。
c.由於HashSet無序,為了實現有序的目的,又不想用其他數據結構,可以用LinkedHashSet。簡要說明,同HashSet和HashMap關系一樣,也是使用了一個LinkedHashMap,LinkedHashMap和普通的HashMap的區別就是,在原有數據結構之上,采用雙向鏈表的形式將所有entry(注意,是前面講過的數組+鏈表中的各個鏈表里的元素,做了連接)連起來,順序就是entry的插入順序,這樣可以保證元素的迭代順序和插入順序相同(有序性)
2.HashMap 是線程安全的嗎,為什么不是線程安全的?
分析:這種問題,既然面試官問起,肯定是在這方面有的聊的。這個問題,在多數情況下,能清晰、全面的描述出問題來龍去脈即可,很少有面試官真的拿一段源碼來考。當然,作為應聘者,如果能理解的很透徹,再用簡單的圖邊寫邊講,作為補充,還是非常出彩的。
答:嗯,不是線程安全的。主要從兩個方面來說:
a.在插入新數據的時候,多線程hash后的結果相同,插入位置也就會定位到數組的相同下標下的同一個鏈表中。在插入時,假如第二個線程在第一個線程插入的瞬間也插入,就可能會導致,覆蓋前面插入的值。
b.第二個非線程安全的影響是在擴容的時候,擴容會把所有值重新hash,插入到新的擴容后的“數組+鏈表”結構中。可能會導致環狀鏈表情況出現,這樣在讀取節點的next節點時,永不為空,也就是著名的擴容時CPU使用率100%情況。
延伸:
要做到心中有底,還是需要知其然知其所以然才行,所以,擼下源碼好好想想,才能做到臨陣不亂。
a.建議好好看看源碼,源碼量不大,結構也比較清晰。
b.針對環狀鏈表的情況,我是看了別人寫的圖文並茂版的文章弄明白的,這里放上鏈接,供參考:HashMap高並發問題
3.問:那你是用什么數據結構來作為替代,滿足線程安全的場景要求呢?
分析:這里一般面試官想考察的是對ConcurrentHashMap的了解,但是也有個別情況,會涉及到HashTable,小端就吃過這方面的虧,明明可以一筆帶過的小知識點,卻由於准備不充分,而沒能答完整。
答:在Java里,提供了以下數據結構,可以解決線程安全問題:
a.HashTable,實現原理是通過Synchronize同步鎖來把讀寫方法都加鎖的方式。雖然線程安全了,但是效率低下,只要有讀寫,就不能做其他操作。
b.SynchronizedMap(涉及較少,了解即可),有條件的同步,是Collections類提供的一個方法返回的HashMap的多線程版本。實現是把基本的方法都加了同步鎖。
c.ConcurrentHashMap(重頭戲):根據jdk版本不同,實現也有差別。
java8以前,是用segment(鎖住map一段實現,默認是16段也就是可以並發數支持到16,也可自定義。讀不受影響),數據結構為數組(segment)+數組(entry)+鏈表,適用於讀多寫少的場景。提供原子操作putIfAbsent()(沒有則添加)。segment繼承自ReentrantLock,來作為鎖。
java8,元素結構為Node(實現Entry接口),數據結構為數組+鏈表;直接對Node進行加鎖,粒度更小。當鏈表長度大於8,轉換為紅黑樹,當然在轉換前,先看下數組長度,如果小於64,那先通過擴容來實現;插入元素時,如果該位置為null,用CAS插入;如果不為null,則加Synchronize鎖插入到鏈表;
擴展:
a.我們看到,數組的默認長度都是16,那么這個數字有什么深意嗎?有!做運算時效率高!屬於取巧的設計。一個是擴容的時候,直接向左位操作,移一位,擴張為二倍;二是hash取模的時候,hash值是32位,右移28位,剩高四位,然后與這個length-1也就是15(1111)按位與操作,使數據均勻分布。
b.ConcurrentHashMap在獲取size時候是怎么計算的呢?
1.7size(),先獲取segment的大小,然后判斷是否修改過,如果是,在加鎖重新獲取segment大小,然后把所有segment大小加在一起;
1.8size()的實現是用一個volatile 變量來做CAS修改,如果高並發,還會把部分值存到一個volatile數組。取值時,把這兩部分的值加在一起。mappingcount()方法和size()方法實現方式一樣
c.hashmap可以使用null作為key和value,存儲於table數組第一個節點;hashtable不允許key和value為null.(這個在一個小公司被面試官問倒了,汗顏)
d.了解一些其他的數據結構:
ConcurrentSkipListMap 數據有序
ConcurrentSkipListSet 能去重
e.hashset是用hashmap實現,內部存的是key,所有的value都是同一個object
好,這篇就先寫到這,大家還有什么想看的,在下面留言告訴我,我會優先整理出來哦。
(期待你的留言交流!點下"推薦",手留余香,謝謝謝謝)
(掃描下方二維碼,關注個人公眾號,第一時間看到你想要的更多專業文章)