1. 程式人生 > 其它 >Java後端高頻知識點學習筆記2---Java集合

Java後端高頻知識點學習筆記2---Java集合

Java後端高頻知識點學習筆記2---Java集合

參考地址:牛_客_網
https://www.nowcoder.com/discuss/819300

1、Java中有哪些集合

Java中的集合類主要由Collection和Map這兩個介面派生出
Collection介面又派生出三個子介面:Set、List、Queue
Set:HashSet、TreeSet
List:ArrayList、LinkedList、Vector
Queue:PriorityQueue、Deque
Map介面下的集合:HashMap、ConcurrentHashMap、Hashtable、TreeMap

2、List、Set、Map三者的區別

List:儲存的元素是有序的、可重複的
Set:儲存的元素是無序的、不可重複的
Map:使用鍵值對儲存;Key是無序的,不可重複的;Value是無序的,可重複的;每個鍵最多對映到一個值

3、ArrayList和LinkedList的區別

(1)ArrayList基於陣列實現,LinkedList基於雙向連結串列實現

(2)對於隨機訪問,ArrayList要優於LinkedList,ArrayList可以根據下標以O(1)時間複雜度對元素進行隨機訪問,而LinkedList的每一個元素都依靠地址指標連線後一個元素,查詢某個元素的時間複雜度是O(N)

(3)對於插入和刪除操作,LinkedList要優於ArrayList

,因為當元素被新增到LinkedList任意位置的時候,不需要像ArrayList那樣重新計算大小或者是更新索引

(4)LinkedList比ArrayList更佔記憶體,因為LinkedList的節點除了儲存資料,還儲存了兩個引用,一個指向前一個元素,一個指向後一個元素

4、有哪些執行緒安全的List

說明
Vector Vector是比較古老的API,雖然保證了執行緒安全,但是由於效率低一般不建議使用
Collections.SynchronizedList SynchronizedList是Collections的內部類,Collections提供了synchronizedList方法,可以將一個執行緒不安全的List包裝成執行緒安全的List,即SynchronizedList;它比Vector有更好的擴充套件性和相容性,但是它所有的方法都帶有同步鎖,也不是效能最優的List
copyOnWriteArrayList CopyOnWriteArrayList是Java 1.5在java.util.concurrent包下增加的類,它採用複製底層陣列的方式來實現寫操作;當執行緒對此類集合執行讀取操作時,執行緒將會直接讀取集合本身,無須加鎖與阻塞;當執行緒對此類集合執行寫入操作時,集合會在底層複製一份新的陣列,接下來對新的陣列執行寫入操作;由於對集合的寫入操作都是對陣列的副本執行操作,因此它是執行緒安全的;在所有執行緒安全的List中,它是效能最優的方案

5、ArrayList和Vector的區別

ArrayList不是執行緒安全的,Vector是執行緒安全的(執行緒安全:當多個執行緒訪問某一個類(物件或方法)時,物件對應的公共資料區始終都能表現正確,那麼這個類(物件或方法)就是執行緒安全的)

ArrayList中的方法不是同步的,Vector中的絕大多數方法都是直接或間接同步的(同步,簡單來說,就是所有的操作都做完,才返回;非同步反之,不等所有操作等做完就返回)

6、ArrayList的擴容機制(基於jdk1.8)

1、ArrayList以無參構造方法建立ArrayList時,初始化賦值的是一個空陣列,對陣列進行新增元素操作時,才真正分配容量;當向ArrayList中新增第一個元素時,陣列容量預設擴容為10

2、當需要擴容時,ArrayList每次擴容都以原來容量的1.5倍進行擴容

3、然後再判斷新容量是否小於最小需要容量,如果還是小於最小需要容量,那就把最小需要容量當作陣列的新容量,即不需要再進行擴容計算

4、然後判斷新容量是否大於設定的最大容量MAX_ARRAY_SIZE
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//-8是為了避免oom
如果已經大於最大容量,則會呼叫hugeCapacity()方法,返回Integer的最大值

5、再通過Arrays.copyOf()方法將原陣列中的內容放到擴容後的新數組裡面

7、HashMap底層實現

分JDK1.7和JDK1.8來答
在JDK1.7時,HashMap的底層資料結構是:陣列+連結串列
在JDK1.8時,HashMap的底層資料結構是:陣列+連結串列+紅黑樹

put操作:
① 首先判斷陣列是否為空,如果陣列為空則進行第一次擴容(resize)
② 根據key計算hash值並與上陣列的長度-1(int index = key.hashCode()&(length-1))得到鍵值對在陣列中的索引
③ 如果該位置為null,則直接插入
④ 如果該位置不為null,則判斷key是否一樣(hashCode和equals),如果一樣則直接覆蓋value
⑤ 如果key不一樣,則判斷該元素是否為紅黑樹的節點,如果是,則直接在紅黑樹中插入鍵值對
⑥ 如果不是紅黑樹的節點,則就是連結串列,遍歷這個連結串列執行插入操作,如果遍歷過程中若發現key已存在,直接覆蓋value即可
如果連結串列的長度大於等於8且陣列中元素數量大於等於閾值64,則將連結串列轉化為紅黑樹,(先在連結串列中插入再進行判斷)
如果連結串列的長度大於等於8且陣列中元素數量小於閾值64,則先對陣列進行擴容,不轉化為紅黑樹
⑦插入成功後,判斷陣列中元素的個數是否大於閾值64(threshold),超過了就對陣列進行擴容操作

get操作:
① 計算key的hashCode的值,找到key在陣列中的位置
② 如果該位置為null,就直接返回null
③ 否則,根據equals()判斷key與當前位置的值是否相等,如果相等就直接返回
④ 如果不等,再判斷當前元素是否為樹節點,如果是樹節點就按紅黑樹進行查詢
⑤ 否則,按照連結串列的方式進行查詢

8、HashMap的擴容機制

1、陣列的初始容量為16,而容量是以2的次方擴充的,一是為了提高效能使用足夠大的陣列,二是為了能使用位運算代替取模預算(據說提升了5~8倍)
2、陣列是否需要擴充是通過負載因子判斷的,如果當前元素個數為陣列容量的0.75時,就會擴充陣列;這個0.75就是預設的負載因子,可由構造器傳入;也可以設定大於1的負載因子,這樣陣列就不會擴充,犧牲效能,節省記憶體
3、為了解決碰撞,陣列中的元素是單向連結串列型別;當連結串列長度到達一個閾值時(7或8),會將連結串列轉換成紅黑樹提高效能;而當連結串列長度縮小到另一個閾值時(6),又會將紅黑樹轉換回單向連結串列提高效能
4、對於第三點補充說明,檢查連結串列長度轉換成紅黑樹之前,還會先檢測當前陣列陣列是否到達一個閾值(64),如果沒有到達這個容量,會放棄轉換,先去擴充陣列。所以上面也說了連結串列長度的閾值是7或8,因為會有一次放棄轉換的操作

9、HashMap為什麼是執行緒不安全的

分JDK1.7和JDK1.8來答
1、在JDK1.7中,當併發執行擴容操作時會造成死迴圈和資料丟失的情況

在JDK1.7中,在多執行緒情況下同時對陣列進行擴容,需要將原來資料轉移到新陣列中,在轉移元素的過程中使用的是頭插法,會造成死迴圈。

2、在JDK1.8中,在併發執行put操作時會發生資料覆蓋的情況

如果執行緒A和執行緒B同時進行put操作,剛好這兩條不同的資料hash值一樣,並且該位置資料為null,所以這執行緒A、B都會通過判斷,將執行插入操作
假設一種情況,執行緒A進入後還未進行資料插入時掛起,而執行緒B正常執行,從而正常插入資料,然後執行緒A獲取CPU時間片,此時執行緒A不用再進行hash判斷了,問題出現:執行緒A會把執行緒B插入的資料給覆蓋,發生執行緒不安全

10、HashMap是如何解決雜湊衝突的

拉鍊法(鏈地址法)
為了解決碰撞,陣列中的元素是單向連結串列型別;當連結串列長度大於等於8時,會將連結串列轉換成紅黑樹提高效能;而當連結串列長度小於等於6時,又會將紅黑樹轉換回單向連結串列提高效能

11、HashMap為什麼使用紅黑樹而不是B樹或平衡二叉樹AVL或二叉查詢樹

1、不使用二叉查詢樹
二叉排序樹在極端情況下會出現線性結構。例如:新增的元素都比根節點小,會導致左子樹線性增長,這樣就失去了用樹型結構替換連結串列的初衷

2、不使用平衡二叉樹
平衡二叉樹是嚴格的平衡樹,紅黑樹是不嚴格平衡的樹,平衡二叉樹在插入或刪除後維持平衡的開銷要大於紅黑樹
雖然紅黑樹查詢效能略低於平衡二叉樹,但在插入和刪除上效能要優於平衡二叉樹
選擇紅黑樹是從功能、效能和開銷上綜合選擇的結果

3、不使用B樹/B+樹
HashMap本來是陣列+連結串列的形式,連結串列由於其查詢慢的特點,所以需要被查詢效率更高的樹結構來替換
如果用B/B+樹的話,在資料量不是很多的情況下,資料都會“擠在”一個結點裡面,這個時候遍歷效率就退化成了連結串列

12、HashMap和Hashtable的區別

① HashMap是⾮執行緒安全的;Hashtable是執行緒安全的,Hashtable 內部的⽅法基本都經過synchronized修飾

② 因為執行緒安全的問題,HashMap要⽐Hashtable效率⾼⼀點

③ HashMap允許鍵和值是null,而Hashtable不允許鍵或值是null
HashMap中,null可以作為鍵,這樣的鍵只有⼀個,可以有⼀個或多個鍵所對應的值為null;HashTable則不允許,當 put 進的鍵或值只要有⼀個 null,直接丟擲 NullPointerException

④ HashMap預設的初始⼤⼩為16,之後每次擴充,容量變為原來的2倍;Hashtable預設的初始⼤⼩為11,之後每次擴充,容量變為原來的2n+1

⑤ 建立時如果給定了容量初始值,那麼 Hashtable 會直接使⽤你給定的⼤⼩;⽽ HashMap 會將其擴充為2的冪次⽅⼤⼩

⑥ JDK1.8 以後的 HashMap 在解決雜湊衝突時當連結串列⻓度⼤於等於8時,將連結串列轉化為紅⿊樹,以減少搜尋時間;Hashtable沒有這樣的機制,Hashtable的底層,是以陣列+連結串列的形式來儲存

⑦ HashMap的父類是AbstractMap,Hashtable的父類是Dictionary

13、ConcurrentHashMap底層實現

JDK1.7

底層資料結構:Segments陣列+HashEntry陣列+連結串列,採用分段鎖保證安全性

一個ConcurrentHashMap中有一個Segments陣列,一個Segments中儲存一個HashEntry陣列,每個HashEntry是一個連結串列結構的元素;segment繼承自ReentrantLock鎖

首先將資料分為一段一段的儲存,然後給每一段資料配一把鎖,當一個執行緒佔用鎖訪問其中一段資料時,其他段的資料也能被其他執行緒訪問,實現了真正的併發訪問

get()操作:
HashEntry中的value屬性和next指標是用volatile修飾的,保證了可見性,所以每次獲取的都是最新值,get()過程不需要加鎖
get()操作流程
1、將key傳入get方法中,先根據key的hashcode的值找到對應的segment段
2、再根據segment中的get方法再次hash,找到HashEntry陣列中的位置
3、最後在連結串列中根據hash值和equals方法進行查詢

ConcurrentHashMap的get操作跟HashMap類似,只是ConcurrentHashMap需要先經過一次hash定位到Segment的位置,然後再hash定位到指定的HashEntry,遍歷該HashEntry下的連結串列進行對比,成功就返回,不成功就返回null

put()操作流程:
1、將key傳入put方法中,先根據key的hashcode的值找到對應的segment段
2、再根據segment中的put方法,加鎖lock()
3、再次hash定位,確定存放的hashEntry陣列中的位置
4、在連結串列中根據hash值和equals方法進行比較,如果相同就直接覆蓋,如果不同就插入在連結串列中

JDK1.8

底層資料結構:Node陣列+連結串列+紅黑樹;採用Synchronized和CAS來保證執行緒安全

get()操作:
get操作全程無鎖;get操作可以無鎖是由於Node元素的值val和指標next是用volatile修飾的

在多執行緒環境下,執行緒A修改節點的val或者新增節點的時候是對執行緒B可見的

get()操作流程
1、計算hash值,定位到Node陣列中的位置
2、如果該位置為null,則直接返回null
3、如果該位置不為null,再判斷該節點是紅黑樹節點還是連結串列節點;如果是紅黑樹節點,使用紅黑樹的查詢方式來進行查詢;如果是連結串列節點,遍歷連結串列進行查詢

put()操作流程
1、先判斷Node陣列有沒有初始化,如果沒有初始化先初始化,執行initTable()方法
2、根據key的進行hash定位,找到Node陣列中的位置,如果不存在hash衝突,即該位置是null,直接CAS插入
3、如果存在hash衝突,就先對連結串列的頭節點或者紅黑樹的頭節點加synchronized鎖
4、如果是連結串列,就遍歷連結串列,如果key相同就執行覆蓋操作,如果不同就將元素插入到連結串列的尾部,並且在連結串列長度大於8, Node陣列的長度超過64時,會將連結串列的轉化為紅黑樹
5、如果是紅黑樹,就按照紅黑樹的結構進行插入

14、ConcurrentHashMap和Hashtable的區別

1、底層資料結構

JDK1.7的ConcurrentHashMap底層採用:Segments陣列 + HashEntry陣列 + 連結串列
JDK1.8的ConcurrentHashMap底層採用:Node資料 + 連結串列 + 紅黑樹
Hashtable底層資料結構採用:陣列 + 連結串列

2.實現執行緒安全的方式

JDK1.7中ConcurrentHashMap採用分段鎖實現執行緒安全
JDK1.8中ConcurrentHashMap採用synchronized和CAS來實現執行緒安全
Hashtable採用synchronized來實現執行緒安全;在方法上加synchronized同步鎖

15、HashSet和TreeSet的異同

相同點
HashSet和TreeSet的元素都是不能重複的,都是執行緒不安全的

不同點
① HashSet中的元素可以為null,但TreeSet中的元素不能為null
② HashSet不能保證元素的排列順序,TreeSet支援自然排序、定製排序兩種排序方式
③ HashSet底層採用雜湊表實現,TreeSet底層採用紅黑樹實現