主頁 > 後端開發 > Java容器集合經典面試題集

Java容器集合經典面試題集

2020-10-01 08:02:36 後端開發

本文總結了Java集合容器的經典面試題,所有題目我都給出了自己思考,適合面試前復習掃盲使用,我不能保證里面包含了所有集合面試題,但只要認真深挖好每一道題,做到觸類旁通,就能以不變應萬變,

  • 大綱:
  • 概述型面試題
  • List
  • Map
  • 小結

概述類面試題

1. 請說一下Java容器集合的分類,各自的繼承結構

  • Java中的容器集合分為兩大陣營,一個是Collection,一個是Map
  • Collection下分為Set,List,Queue
  • Set的常用實作類有HashSet,TreeSet等
  • List的常用實作類有ArrayList,LinkedList等
  • Queue的常用實作類有LinkedList,ArrayBlockingQueue等
  • Map下沒有進一步分類,它的常用實作類有HashMap,ConcurrentHashMap等

能把上面的基本框架答出來基本就沒問題了,對于各種型別我只列舉了一些實際作業中常用的實作類,但其實在Set,List和Queue下還有更細的劃分,如果想要在面試時表現一番,那得對著JDK好好背一背了>_<

2. 請談一談Java集合中的fail-fast和fail-safe機制

fail-fast是一種錯誤檢測機制,Java在適合單執行緒使用的集合容器中很好地實作了fail-fast機制,舉一個簡單的例子:在多執行緒并發環境下,A執行緒在通過迭代器遍歷一個ArrayList集合,B執行緒同時對該集合進行增刪元素操作,這個時候執行緒A就會拋出并發修改例外,中斷正常執行的邏輯,

而fail-safe機制更像是一種對fail-fast機制的補充,它被廣泛地實作在各種并發容器集合中,回頭看上面的例子,如果執行緒A遍歷的不是一個ArrayList,而是一個CopyOnWriteArrayList,則符合fail-safe機制,執行緒B可以同時對該集合的元素進行增刪操作,執行緒A不會拋出任何例外,

要理解這兩種機制的表象,我們得了解這兩種機制背后的實作原理:

我們同樣用ArrayList解釋fail-fast背后的原理:首先ArrayList自身會維護一個modCount變數,每當進行增刪元素等操作時,modCount變數都會進行自增,當使用迭代器遍歷ArrayList時,迭代器會新維護一個初始值等于modCount的expectedModCount變數,每次獲取下一個元素的時候都會去檢查expectModCount和modCount是否相等,在上面舉的例子中,由于B執行緒增刪元素會導致modCount自增,當A執行緒遍歷元素時就會發現兩個變數不等,從而拋出例外,

CopyOnWriteArrayList所實作的fail-safe在上述情況下沒有拋出例外,它的原理是:當使用迭代器遍歷集合時,會基于原陣列拷貝出一個新的陣列(ArrayList的底層是陣列),后續的遍歷行為在新陣列上進行,所以執行緒B同時進行增刪操作不會影響到執行緒A的遍歷行為,

這種題目我覺得要先答出核心原理,如果你對多執行緒和單執行緒下容器的使用有自己的見解,可以考慮多聊點,

3. 如何一邊遍歷一邊洗掉Collection中的元素?

使用集合迭代器自身的remove方法進行洗掉

Iterator<Integer> it = list.iterator();
while(it.hasNext()){
   *// do something*
   it.remove();
}

可能筆試考的更多,算是Java的基本常識吧

List類面試題

4. 談談ArrayList和LinkedList的區別

本質的區別來源于兩者的底層實作:ArrayList的底層是陣列,LinkedList的底層是雙向鏈表,

陣列擁有O(1)的查詢效率,可以通過下標直接定位元素;鏈表在查詢元素的時候只能通過遍歷的方式查詢,效率比陣列低,

陣列增刪元素的效率比較低,通常要伴隨拷貝陣列的操作;鏈表增刪元素的效率很高,只需要調整對應位置的指標即可,

以上是陣列和鏈表的通俗對比,在日常的使用中,兩者都能很好地在自己的適用場景發揮作用,

比如說我們常常用ArrayList代替陣列,因為封裝了許多易用的api,而且它內部實作了自動擴容機制,由于它內部維護了一個當前容量的指標size,直接往ArrayList中添加元素的時間復雜度是O(1)的,使用非常方便,

而LinkedList常常被用作Queue佇列的實作類,由于底層是雙向鏈表,能夠輕松地提供先入先出的操作,

我覺得可以分兩部分答,一個是陣列與鏈表底層實作的不同,另一個是答ArrayList和LinkedList的實作細節,

5. 談談ArrayList和Vector的區別

兩者的底層實作相似,關鍵的不同在于Vector的對外提供操作的方法都是用synchronized修飾的,也就是說Vector在并發環境下是執行緒安全的,而ArrayList在并發環境下可能會出現執行緒安全問題,

由于Vector的方法都是同步方法,執行起來會在同步上消耗一定的性能,所以在單執行緒環境下,Vector的性能是不如ArrayList的

除了執行緒安全這點本質區別外,還有一個實作上的小細節區別:ArrayList每次擴容的大小為原來的1.5倍;Vector可以指定擴容的大小,默認是原來大小的兩倍,

感覺可以順帶談談多執行緒環境下ArrayList的替代品,比如CopyOnWriteArrayList,但是要談談優缺點,

6. 為什么ArrayList的elementData陣列要加上transient修飾

由于ArrayList有自動擴容機制,所以ArrayList的elementData陣列大小往往比現有的元素數量大,如果不加transient直接序列化的話會把陣列中空余的位置也序列化了,浪費不少的空間,

ArrayList中重寫了序列化和反序列化對應的writeObject和readObject方法,在遍歷陣列元素時,以size作為結束標志,只序列化ArrayList中已經存在的元素,

細節題

Map類面試題

HashMap死亡連環Call即將來臨,看爽了記得點個贊啊

7. 請介紹一下HashMap的實作原理

  1. 我們一般用HashMap存盤key-value型別的資料,它的底層是一個陣列,當我們呼叫put方法的時候,首先會對key進行計算得出一個hash值,然后根據hash值計算出存放在陣列上的位置
  2. 這個時候我們會遇到兩種情況:一是陣列上該位置為空,可以直接放入資料;還有一種情況是該位置已經存放值了,這就發生了哈希沖突,
  3. 在現在使用較為普遍的JDK1.8中是這樣處理哈希沖突的:先用鏈表把沖突的元素串起來,如果鏈表的長度達到了8,并且哈希表的長度大于64,則把鏈表轉為紅黑樹,(在JDK1.7中沒有轉化為紅黑樹這一步,只用鏈表解決沖突)

先熱身

8. HashMap是怎樣確定key存放在陣列的哪個位置的?

JDK1.8

首先計算key的hash值,計算程序是:先得到key的hashCode(int型別,4位元組),然后把hashCode的高16位與低16位進行異或,得到key的hash值,

接下來用key的hash值與陣列長度減一的值進行按位與操作,得到key在陣列中對應的下標,

追問:為什么計算key的hash時要把hashCode的高16位與低16位進行異或?(變式:為什么不直接用key的hashCode)

計算key在陣列中的下標時,是通過hash值與陣列長度減一的值進行按位與操作的,由于陣列的長度通常不會超過2^16,所以hash值的高16位通常參與不了這個按位與操作,

為了讓hashCode的高16位能夠參與到按位與操作中,所以把hashCode的高16位與低16位進行異或操作,使得高16位的影響能夠均勻稀釋到低16位中,使得計算key位置的操作能夠充分散列均勻,

9. 為什么要把鏈表轉為紅黑樹,閾值為什么是8?

在極端情況下,比如說key的hashCode()回傳的值不合理,或者多個密鑰共享一個hashCode,很有可能會在同一個陣列位置產生嚴重的哈希沖突,

這種情況下,如果我們仍然使用使用鏈表把多個沖突的元素串起來,這些元素的查詢效率就會從O(1)下降為O(N),為了能夠在這種極端情況下仍保證較為高效的查詢效率,HashMap選擇把鏈表轉換為紅黑樹,紅黑樹是一種常用的平衡二叉搜索樹,添加,洗掉,查找元素等操作的時間復雜度均為O(logN)

至于閾值為什么是8,這是HashMap的作者根據概率論的知識得到的,當key的哈希碼分布均勻時,陣列同一個位置上的元素數量是成泊松分布的,同一個位置上出現8個元素的概率已經接近千分之一了,這側面說明如果鏈表的長度達到了8,key的hashCode()肯定是出了大問題,這個時候需要紅黑樹來保證性能,所以選擇8作為閾值,

追問:為什么紅黑樹轉換回鏈表的閾值不是7而是6呢?

如果是7的話,那么鏈表和紅黑樹之間的切換范圍值就太小了,如果我的鏈表長度不停地在7和8之間切換,那豈不是得來回變換形態?所以選擇6是一種折中的考慮,

10. 請說一下HashMap的擴容原理

  1. 首先得到新的容量值和新的擴容閾值,默認都是原來大小的兩倍,
  2. 然后根據新容量創建新的陣列
  3. 最后把元素從舊陣列中遷移到新陣列中

在JDK1.7中,遷移資料的時候所有元素都重新計算了hash,并根據新的hash重新計算陣列中的位置,

在JDK1.8中,這個程序進行了優化:如果當前節點是單獨節點(后面沒有接著鏈表),則根據該節點的hash值與新容量減一的值按位與得到新的地址,

如果當前節點后面帶有鏈表,則根據每個節點的hash值與舊陣列容量進行按位與的結果進行劃分,如果得到的值為0,這些元素會被分配回原來的位置;如果得到的結果不為0,則分配到新位置,新位置的下標為當前位置下標加上舊陣列容量,

還有一種情況是當前節點是樹節點,那么會呼叫一個專門的拆分方法進行拆分,

追問:為什么HashMap不支持動態縮容?

開放性題目?以下是個人見解:

如果要支持動態縮容,可能就要把縮容安排在remove方法里,這樣可能會導致remove方法的時間復雜度從O(1)上升為O(N),

還有一點可能和我們撰寫Java代碼的習慣有關:由于Java有自動垃圾回識訓制,讓我們得以可勁地new物件,Java也默認了我們這種吃飯不收拾盤子的行為,既然物件會被回收,HashMap動態縮容在這樣的大環境下似乎就顯得沒那么重要了,這可以說是一種空間換時間的策略吧,

11. 為什么HashMap中適合用Integer,String這樣的基礎型別作為key?

因為這些基礎類內部已經重寫了hashCode和equals方法,遵守了HashMap內部的規范,

追問:如果要用我們自己實作的類作為key,要注意什么?

一定要重寫hashCode()和equals()方法,而且要遵從以下規則:

equals()是我們判斷兩個物件是否相同的依據,如果我們重寫了equals方法,用自己的邏輯去判斷兩個物件是否相同,那么一定要保證:

兩個equals()回傳true的物件,一定要回傳相同的hashCode,

這樣,在HashMap的put方法中才能正確判斷key是否相同,

不是經常有一個問題嘛,兩個物件hashCode相同,equals一定回傳true嗎?答案肯定是否的,這和你的設計密切相關:如果在你的編程思路中這兩個物件是不同的,那么就算恰巧兩個物件的hashCode相同,equals也應該回傳false,

12. 為什么HashMap陣列的長度是2的冪次方?

因為這樣能夠提高根據key計算陣列位置的效率,

HashMap根據key計算陣列位置的演算法是:用key的hash值與陣列長度減1的值進行按位與操作,

在我們正常人的思維中,獲取陣列的某個位置最直接的方法是對陣列的長度取余數,但是如果被除數是2的冪次方,那么這個對陣列長度取余的方法就等價于對陣列長度減一的值進行按位與操作,

在計算機中,位運算的效率遠高于取模運算,所以為了提高效率,把陣列的長度設為2的冪次方,

13. HashMap與HashTable有什么區別?

在JDK1.7之前,兩者的實作極為相似,最大的區別在于HashTable的方法都用synchronized關鍵字修飾起來了,表明它是執行緒安全的,

但是由于直接在方法上加synchronized關鍵字的同步效率較低,在并發情況下,官方推薦我們使用ConcurrentHashMap,

所以我們看到在JDK1.8中,官方甚至沒有對HashTable進行鏈表轉樹這樣的優化,HashTable已經不被推薦使用了,

14. 請說一下ConcurrentHashMap的實作原理

在JDK1.7中ConcurrentHashMap采用了一種分段鎖的機制,它的底層實作是一個segment陣列,每個segment的底層結構和HashMap相似,也是陣列加鏈表,

當對segment里面的元素進行操作之前,需要獲得該segment獨有的一把ReentrantLock,ConcurrentHashMap如果不進行手動設定的話,默認有16個segment,可以支持16個執行緒對16個不同的segment進行并發寫操作,

在JDK1.8之后摒棄了segment這種臃腫的設計,新的實作和HashMap非常相似,底層用的也是陣列加鏈表加紅黑樹,

在新實作中,在put方法里使用了CAS + synchronized進行同步,如果插入元素的位置為空,則使用CAS進行插入,如果插入的位置不為空,則對當前位置的物件進行加鎖,也就鏈表或紅黑樹的頭節點,加鎖后再進行后續的插入操作,

這樣設計的好處是:

  1. CAS是十分輕量的加鎖操作,如果能夠直接插入,用CAS能夠大幅度節省加鎖的開銷,
  2. 如果發生沖突,只用鎖住當前位置的頭結點,理論上陣列的長度有多大,并發操作的執行緒數就能有多少,比原來只能有16個執行緒效率更高,

這道題如果想深挖擴展可以開始往Java多執行緒并發方面扯:synchronized,CAS,Java多執行緒方面我也會出一份總結,有興趣的不妨先點贊關注一波

小結

我感覺面試的時候對集合的考察會偏向實作原理多一些,所以一定要看一遍原始碼,相比于框架的原始碼,集合的原始碼簡直太友好了,在筆試的時候可能還會考一些集合的使用,比如遍歷,排序,比較等等,這些算是Java基礎了,用得多也就熟了,

最后如果你覺得我的回答有問題,歡迎指正!

愿各位有所識訓,共同進步,共勉!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/144541.html

標籤:Java

上一篇:Java Agent入門

下一篇:SpringBoot2 整合JTA組件,多資料源事務管理

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more