「實實在在面試」—List和Map集合面試合集【含講解視訊】
前言
Tip:該筆記為B站面試講解視訊的配套文件,B站搜尋”程式設計鹿“可以看到面試題講解視訊
視訊地址如下:奧利給 程式設計人—2020年Java大廠面試題集錦(面試必備 持續更新)
https://www.bilibili.com/video/BV15i4y1L7Bt
什麼是陣列
陣列(Array)是一種線性表資料結構。它用一組連續的記憶體空間,來儲存一組具有相同型別的資料。
特點:
- 線性表 實體記憶體上連續還是邏輯上連續的資料結構都稱之為線性表
- 連續的記憶體空間和相同型別的資料
優點:
-
按照下標訪問快「隨機訪問」(直接訪問 任意訪問)
假如:每個元素佔據長度 為 3,第一個元素開始地址編號為 10
那麼只要知道下標 就可以快速計算出來運算的地址
第三個元素位置為:10+2*3 =16
缺點:
-
資料插入 需要擴容和挪動元素
-
刪除元素 也需要挪動元素
什麼是連結串列
連結串列通過指標將一組零散的記憶體塊串聯在一起的線性資料結構,把記憶體塊稱為連結串列的“結點”。為了將所有的結點串起來,每個連結串列的結點除了儲存資料之外,還需要記錄鏈上的下一個結點的地址,記錄下個結點地址的指標叫作後繼指標 next。
如下圖所示:
特點:
- 線性表 通過“指標”將一組零散的記憶體塊串聯
優點:
- 增刪快 不需要元素搬移
缺點:
- 查詢慢 需要通過前結點 獲取後結點的地址
其他連結串列:
-
迴圈連結串列
-
雙向連結串列 插入 刪除更加高效 查詢可以雙向遍歷
ArrayList 和 LinkedList 的區別
資料結構實現
- ArrayList 是陣列的資料結構實現
- LinkedList 是雙向連結串列的資料結構實現。
訪問效率
- ArrayList 比LinkedList 在下標訪問的時候效率要高
- LinkedList 是線性的資料儲存方式,所以需要移動指標從前往後依次查詢。
增加和刪除效率
- 在非首尾的增加和刪除操作,LinkedList 要比ArrayList效率要高,因為ArrayList增刪操作要影響陣列內的其他資料的下標。.
綜合來說,在需要頻繁讀取集合中的元素時,更推薦使用ArrayList,而在插入和刪除操作較多時,更推薦使用LinkedList。
ArrayList 初始化長度多少
ArrayList 底層是陣列,ArrayList 初始長度為 10
-
1.8 之前 ArrayList 初始長度為 10
-
1.8 之後
-
通過無參構造方法第一次建立集合的時候不會建立底層長度為10的陣列
-
在第一次新增的元素的時候 建立底層陣列 長度為10
-
ArrayList 如何新增元素
按照下標新增,每次新增都會判斷集合的容量
- 第一次新增 會建立長度為10的底層陣列
- 後續新增 如果容量不足 會擴容
ArrayList 如何擴容
什麼時候發生擴容:
-
每次新增的時候 會檢查要不要擴容 執行ensureCapacityInternal 確保內部容量
-
ensureCapacityInternal 會判斷陣列長度夠不夠 不夠就擴容
擴容流程:
-
判斷陣列長度夠不夠新增新元素
-
不夠就觸發擴容 grow方法
//真正的擴容 private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; //新的容量是在原有的容量基礎上+50% 右移一位就是二分之一 int newCapacity = oldCapacity + (oldCapacity >> 1); //如果新容量小於最小容量,按照最小容量進行擴容 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: //這裡是重點 呼叫工具類Arrays的copyOf擴容 elementData = Arrays.copyOf(elementData, newCapacity); }
- 擴容 1.5 倍
- 擴容需要賦值資料
建立集合的時候如果能夠預估長度,最好指定集合大小
ArrayList 和 Vector 的區別
ArrayList 執行緒不安全
Vector 執行緒安全 Vector幾乎所有的方法都加了鎖
除了 Vector 還有什麼執行緒安全的List
CopyOnWriteArrayList 讀不加鎖寫加鎖
複製寫:複製一個新陣列 將元素新增新陣列中
public boolean add(E e) {
建立鎖物件
final ReentrantLock lock = this.lock;
加鎖
lock.lock();
try {
獲取現有陣列
Object[] elements = getArray();
獲取現有陣列長度
int len = elements.length;
根據現有陣列得到一個長度+1的新陣列 【複製】
Object[] newElements = Arrays.copyOf(elements, len + 1);
將要新增的元素 寫入新陣列中
newElements[len] = e;
用新陣列替換老陣列
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
HashMap的底層結構
1.7 陣列+連結串列
1.8 陣列+連結串列+紅黑樹
如果連結串列的長度大於8 連結串列就會處理為樹結構
HashMap 預設初始化大小是多少?
預設初始值 16
HashMap的主要引數都有哪些?
預設初始值(陣列的長度):16
負載因子:0.75
擴容的條件:當底層陣列 四分之三 的位置有了元素 就擴容
HashMap是怎麼處理hash碰撞的?
hash碰撞,兩個key計算的結果都是同一個下標
- 連結串列
- 連結串列+樹
HashMap 新的Entry節點在插入連結串列的時候,是怎麼插入的
Entry
- 在JDK8之前是頭插法,新的值會取代原有的值,原有的值會被推到連結串列上
- 在JDK8之後是尾插法
- 頭插法可能出現迴圈連結串列的問題
- 使用頭插會改變連結串列的上的順序,但是如果使用尾插,在擴容時會保持連結串列元素原本的順序,就不會出現連結串列成環的問題了。
- Java7在多執行緒操作 HashMap 時可能引起死迴圈,原因是擴容轉移後前後連結串列順序倒置,在轉移過程中修改了原來連結串列中節點的引用關係。
HashMap的擴容機制?
-
什麼時候擴容?(risize)
HashMap的底層是陣列,陣列的容量有限,到達一定的數量就會進行擴容。
影響擴容時機的因素有兩個:
- Capacity:HashMap當前長度
- LoadFactor:負載因子,預設值0.75f
什麼意思呢?當陣列中75%的位置滿了的時候,就會進行擴容。想要晚的觸發擴容就只能調高負載因子。
-
怎麼擴容?
擴容分為兩步
-
擴容:建立一個新的Entry空陣列,長度是原陣列的2倍
-
ReHash:遍歷原Entry陣列,把所有的Entry重新Hash到新陣列
進行重新Hash的原因:Hash的計算和陣列的長度有關(對長度位運算)HashCode(Key) & (Length - 1)。所以長度改變了,所有的元素複製到新陣列中需要重新計算位置
-
HashMap 執行緒安全嗎?
不是
有哪些執行緒安全的 Map
Hashtable
ConcurrentHashMap
ConcurrentHashMap 基本原理
1.7 分段鎖
1.8 CAS 無鎖演算法(樂觀鎖)
加不加鎖為條件進行分類
悲觀鎖 確實加鎖了 一個執行緒操作的時候會持有鎖物件 其他執行緒需要等到拿到鎖物件的時候才能操作元素
樂觀鎖 演算法控制 邏輯鎖
原子性 Integer AtomicInteger 實現原子性的自增
HashSet
HashSet 底層是HashMap 只使用HashMap的key 不使用value
Queue
Queue ---》執行緒池
「❤️ 帥氣的你又來看了我」
如果你覺得這篇內容對你挺有有幫助的話:
-
點贊支援下吧,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓 -_-)
-
歡迎在留言區與我分享你的想法,也歡迎你在留言區記錄你的思考過程。