前言
立刻到今年的金三銀四了,又是跳槽的好季節,準備跳槽的同窗都摩拳擦掌準備大面好幾場,本次小編爲你們準備了精選的 Java 集合面試題,快來查漏補缺吧。java
小編分享的這份金三銀四Java後端開發面試總結包含了JavaOOP、Java集合容器、Java異常、併發編程、Java反射、Java序列化、JVM、Redis、Spring MVC、MyBatis、MySQL數據庫、消息中間件MQ、Dubbo、Linux、ZooKeeper、 分佈式&數據結構與算法等26個專題技術點,都是小編在各個大廠總結出來的面試真題,已經有不少粉絲靠這份PDF拿下衆多大廠的offer,今天在這裏總結分享給到你們!【持續更新中!】node
完整版Java面試題地址:2021最新面試題合集集錦。面試
1、集合容器概述
1. 什麼是集合
- 集合就是一個放數據的容器,準確的說是放數據對象引用的容器
- 集合類存放的都是對象的引用,而不是對象的自己
- 集合類型主要有3種:set(集)、list(列表)和map(映射)。
2. 集合的特色
集合的特色主要有以下兩點: 算法
- 集合用於存儲對象的容器,對象是用來封裝數據,對象多了也須要存儲集中式管理。
- 和數組對比對象的大小不肯定。由於集合是可變長度的。數組須要提早定義大小
3. 集合和數組的區別
- 數組是固定長度的;集合可變長度的。
- 數組能夠存儲基本數據類型,也能夠存儲引用數據類型;集合只能存儲引用數據類型。
- 數組存儲的元素必須是同一個數據類型;集合存儲的對象能夠是不一樣數據類型。
4. 使用集合框架的好處
- 容量自增加;
- 提供了高性能的數據結構和算法,使編碼更輕鬆,提升了程序速度和質量;
- 能夠方便地擴展或改寫集合,提升代碼複用性和可操做性。
- 經過使用JDK自帶的集合類,能夠下降代碼維護和學習新API成本。
5. 經常使用的集合類有哪些?
Map接口和Collection接口是全部集合框架的父接口: 數據庫
- Collection接口的子接口包括:Set接口和List接口
- Map接口的實現類主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
- Set接口的實現類主要有:HashSet、TreeSet、LinkedHashSet等
- List接口的實現類主要有:ArrayList、LinkedList、Stack以及Vector等
6. List,Set,Map三者的區別?
7. 集合框架底層數據結構
8. 哪些集合類是線程安全的?
- Vector:就比Arraylist多了個 synchronized (線程安全),由於效率較低,如今已經不太建議使用。
- hashTable:就比hashMap多了個synchronized (線程安全),不建議使用。
- ConcurrentHashMap:是Java5中支持高併發、高吞吐量的線程安全HashMap實現。它由Segment數組結構和HashEntry數組結構組成。Segment數組在ConcurrentHashMap裏扮演鎖的角色,HashEntry則用於存儲鍵-值對數據。一個ConcurrentHashMap裏包含一個Segment數組,Segment的結構和HashMap相似,是一種數組和鏈表結構;一個Segment裏包含一個HashEntry數組,每一個HashEntry是一個鏈表結構的元素;每一個Segment守護着一個HashEntry數組裏的元 素,當對HashEntry數組的數據進行修改時,必須首先得到它對應的Segment鎖。(推薦使用)
9. Java集合的快速失敗機制 「fail-fast」?
10. 怎麼確保一個集合不能被修改?
- 可使用 Collections. unmodififiableCollection(Collection c) 方法來建立一個只讀集合,這樣改變集合的任何操做都會拋出 Java. lang. UnsupportedOperationException 異常。
- 示例代碼以下:
List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 運行時此行報錯
System. out. println(list. size());
2、Collection接口編程
List接口
11. 迭代器 Iterator 是什麼?
12. Iterator 怎麼使用?有什麼特色?
Iterator 使用代碼以下: 後端
List<String> list = new ArrayList<>();
Iterator<String> it = list. iterator();
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}
Iterator 的特色是隻能單向遍歷,可是更加安全,由於它能夠確保,在當前遍歷的集合元素被更改的時候,就會拋出 ConcurrentModifificationException 異常。 設計模式
13. 如何邊遍歷邊移除 Collection 中的元素?
14. Iterator 和 ListIterator 有什麼區別?
- Iterator 能夠遍歷 Set 和 List 集合,而 ListIterator 只能遍歷 List。
- Iterator 只能單向遍歷,而 ListIterator 能夠雙向遍歷(向前/後遍歷)。
- ListIterator 實現 Iterator 接口,而後添加了一些額外的功能,好比添加一個元素、替換一個元素、獲取前面或後面元素的索引位置。
15. 遍歷一個 List 有哪些不一樣的方式?每種方法的實現原理是什麼?Java 中 List 遍歷的最佳實踐是什麼?
16. 說一下 ArrayList 的優缺點
17. 如何實現數組和 List 之間的轉換?
- 數組轉 List:使用 Arrays. asList(array) 進行轉換。
- List 轉數組:使用 List 自帶的 toArray() 方法。
- 代碼示例:
// list to array
List<String> list = new ArrayList<String>();
list.add("123");
list.add("456");
list.toArray();
// array to list
String[] array = new String[]{"123","456"};
Arrays.asList(array);
18. ArrayList 和 LinkedList 的區別是什麼?
- 數據結構實現:ArrayList 是動態數組的數據結構實現,而 LinkedList 是雙向鏈表的數據結構實現。
- 隨機訪問效率:ArrayList 比 LinkedList 在隨機訪問的時候效率要高,由於 LinkedList 是線性的數據存儲方式,因此須要移動指針從前日後依次查找。
- 增長和刪除效率:在非首尾的增長和刪除操做,LinkedList 要比 ArrayList 效率要高,由於ArrayList 增刪操做要影響數組內的其餘數據的下標。
- 內存空間佔用:LinkedList 比 ArrayList 更佔內存,由於 LinkedList 的節點除了存儲數據,還存儲了兩個引用,一個指向前一個元素,一個指向後一個元素。
- 線程安全:ArrayList 和 LinkedList 都是不一樣步的,也就是不保證線程安全;
- 綜合來講,在須要頻繁讀取集合中的元素時,更推薦使用 ArrayList,而在插入和刪除操做較多時,更推薦使用 LinkedList。
- LinkedList 的雙向鏈表也叫雙鏈表,是鏈表的一種,它的每一個數據結點中都有兩個指針,分別指向直接後繼和直接前驅。因此,從雙向鏈表中的任意一個結點開始,均可以很方便地訪問它的前驅結點和後繼結點。
19. ArrayList 和 Vector 的區別是什麼?
20. 插入數據時,ArrayList、LinkedList、Vector誰速度較快?闡述 ArrayList、Vector、LinkedList 的存儲性能和特性?
- ArrayList和Vector 底層的實現都是使用數組方式存儲數據。數組元素數大於實際存儲的數據以便增長和插入元素,它們都容許直接按序號索引元素,可是插入元素要涉及數組元素移動等內存操做,因此索引數據快而插入數據慢。
- Vector 中的方法因爲加了 synchronized 修飾,所以 Vector 是線程安全容器,但性能上較ArrayList差。
- LinkedList 使用雙向鏈表實現存儲,按序號索引數據須要進行前向或後向遍歷,但插入數據時只須要記錄當前項的先後項便可,因此 LinkedList 插入速度較快。
21. 多線程場景下如何使用 ArrayList?
- ArrayList 不是線程安全的,若是遇到多線程場景,能夠經過 Collections 的 synchronizedList 方法將其轉換成線程安全的容器後再使用。例如像下面這樣:
List<String> synchronizedList = Collections.synchronizedList(list);
synchronizedList.add("aaa");
synchronizedList.add("bbb");
for (int i = 0; i < synchronizedList.size(); i++) {
System.out.println(synchronizedList.get(i));
}
22. 爲何 ArrayList 的 elementData 加上 transient 修飾?
private transient Object[] elementData; 數組
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- 能夠看到 ArrayList 實現了 Serializable 接口,這意味着 ArrayList 支持序列化。transient 的做用是說不但願 elementData 數組被序列化,重寫了 writeObject 實現:
private void writeObject(java.io.ObjectOutputStream s) throws
java.io.IOException{
*// Write out element count, and any hidden stuff*
int expectedModCount = modCount;
s.defaultWriteObject();
*// Write out array length*
s.writeint(elementData.length);
*// Write out all elements in the proper order.*
for (int i=0; i<size; i++)
s.writeObject(elementData[i]);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
- 每次序列化時,先調用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,而後遍歷 elementData,只序列化已存入的元素,這樣既加快了序列化的速度,又減少了序列化以後的文件大小。
23. List 和 Set 的區別
Set接口
24. 說一下 HashSet 的實現原理?
- HashSet 是基於 HashMap 實現的,HashSet的值存放於HashMap的key上,HashMap的value統一爲present,所以 HashSet 的實現比較簡單,相關 HashSet 的操做,基本上都是直接調用底層HashMap 的相關方法來完成,HashSet 不容許重複的值。
25. HashSet如何檢查重複?HashSet是如何保證數據不可重複的?
- 向HashSet 中add ()元素時,判斷元素是否存在的依據,不只要比較hash值,同時還要結合equles 方法比較。
- HashSet 中的add ()方法會使用HashMap 的put()方法。
- HashMap 的 key 是惟一的,由源碼能夠看出 HashSet 添加進去的值就是做爲HashMap 的key,而且在HashMap中若是K/V相同時,會用新的V覆蓋掉舊的V,而後返回舊的V。因此不會重複(HashMap 比較key是否相等是先比較hashcode 再比較equals )。
- 如下是HashSet 部分源碼:
private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;
public HashSet() {
map = new HashMap<>();
}
public Boolean add(E e) {
// 調用HashMap的put方法,PRESENT是一個至始至終都相同的虛值
return map.put(e, PRESENT)==null;
}
26. HashSet與HashMap的區別
3、Map接口
27. 什麼是Hash算法
- 哈希算法是指把任意長度的二進制映射爲固定長度的較小的二進制值,這個較小的二進制值叫作哈希值。
28. 什麼是鏈表
29. 說一下HashMap的實現原理?
30. HashMap在JDK1.7和JDK1.8中有哪些不一樣?HashMap的底層實現
31. 什麼是紅黑樹
說道紅黑樹先講什麼是二叉樹 緩存
紅黑樹
- 紅黑樹是一種特殊的二叉查找樹。紅黑樹的每一個結點上都有存儲位表示結點的顏色,能夠是紅 (Red)或黑(Black)。
- 紅黑樹的每一個結點是黑色或者紅色。當是無論怎麼樣他的根結點是黑色。每一個葉子結點(葉子結點表明終結、結尾的節點)也是黑色 注意:這裏葉子結點,是指爲空(NIL或NULL)的葉子結點!。
- 若是一個結點是紅色的,則它的子結點必須是黑色的。
- 每一個結點到葉子結點NIL所通過的黑色結點的個數同樣的。確保沒有一條路徑會比其餘路徑長出倆倍,因此紅黑樹是相對接近平衡的二叉樹的!
- 紅黑樹的基本操做是添加、刪除。在對紅黑樹進行添加或刪除以後,都會用到旋轉方法。爲何呢?道理很簡單,添加或刪除紅黑樹中的結點以後,紅黑樹的結構就發生了變化,可能不知足上面三條性質,也就再也不是一顆紅黑樹了,而是一顆普通的樹。而經過旋轉和變色,可使這顆樹從新成爲紅黑樹。簡單點說,旋轉和變色的目的是讓樹保持紅黑樹的特性。
32. HashMap的put方法的具體流程?
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//實現Map.put和相關方法
final V putVal(int hash, K key, V value, Boolean onlyIfAbsent,
Boolean evict) {
Node<K,V>[] tab;
Node<K,V> p;
int n, i;
// 步驟①:tab爲空則建立
// table未初始化或者長度爲0,進行擴容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 步驟②:計算index,並對null作處理
// (n - 1) & hash 肯定元素存放在哪一個桶中,桶爲空,新生成結點放入桶中(此時,這個結點是放在數
組中)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 桶中已經存在元素 else {
Node<K,V> e;
K k;
// 步驟③:節點key存在,直接覆蓋value
// 比較桶中第一個元素(數組中的結點)的hash值相等,key相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 將第一個元素賦值給e,用e來記錄
e = p;
// 步驟④:判斷該鏈爲紅黑樹
// hash值不相等,即key不相等;爲紅黑樹結點
// 若是當前元素類型爲TreeNode,表示爲紅黑樹,putTreeVal返回待存放的node, e可能爲null else if (p instanceof TreeNode)
// 放入樹中
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 步驟⑤:該鏈爲鏈表
// 爲鏈表結點 else {
// 在鏈表最末插入結點
for (int binCount = 0; ; ++binCount) {
// 到達鏈表的尾部
//判斷該鏈表尾部指針是否是空的
if ((e = p.next) == null) {
// 在尾部插入新結點
p.next = newNode(hash, key, value, null);
//判斷鏈表的長度是否達到轉化紅黑樹的臨界值,臨界值爲8
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//鏈表結構轉樹形結構
treeifyBin(tab, hash);
// 跳出循環
break;
}
// 判斷鏈表中結點的key值與插入的元素的key值是否相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 相等,跳出循環
break;
// 用於遍歷桶中的鏈表,與前面的e = p.next組合,能夠遍歷鏈表
p = e;
}
}
//判斷當前的key已經存在的狀況下,再來一個相同的hash值、key值時,返回新來的value這個
值
if (e != null) {
// 記錄e的value
V oldValue = e.value;
// onlyIfAbsent爲false或者舊值爲null
if (!onlyIfAbsent || oldValue == null)
//用新值替換舊值
e.value = value;
// 訪問後回調
afterNodeAccess(e);
// 返回舊值
return oldValue;
}
}
// 結構性修改
++modCount;
// 步驟⑥:超過最大容量就擴容
// 實際大小大於閾值則擴容
if (++size > threshold)
resize();
// 插入後回調
afterNodeInsertion(evict);
return null;
}
- 判斷鍵值對數組tablei是否爲空或爲null,不然執行resize()進行擴容;
- 根據鍵值key計算hash值獲得插入的數組索引i,若是tablei==null,直接新建節點添加,轉向⑥,若是tablei不爲空,轉向③;
- 判斷tablei的首個元素是否和key同樣,若是相同直接覆蓋value,不然轉向④,這裏的相同指的是hashCode以及equals;
- 判斷tablei 是否爲treeNode,即tablei 是不是紅黑樹,若是是紅黑樹,則直接在樹中插入鍵值對,不然轉向5;
- 遍歷tablei,判斷鏈表長度是否大於8,大於8的話把鏈表轉換爲紅黑樹,在紅黑樹中執行插入操做,不然進行鏈表的插入操做;遍歷過程當中若發現key已經存在直接覆蓋value便可;
- 插入成功後,判斷實際存在的鍵值對數量size是否超多了最大容量threshold,若是超過,進行擴容。
33. HashMap的擴容操做是怎麼實現的?
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
//oldTab指向hash桶數組
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
//若是oldCap不爲空的話,就是hash桶數組不爲空
if (oldCap >= MAXIMUM_CAPACITY) {
//若是大於最大容量了,就賦值爲整數最大的閥值
threshold = Integer.MAX_VALUE;
return oldTab;
//返回
}
//若是當前hash桶數組的長度在擴容後仍然小於最大容量 而且oldCap大於默認值16 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1;
// double threshold 雙倍擴容閥值threshold
}
// 舊的容量爲0,但threshold大於零,表明有參構造有cap傳入,threshold已經被初始化
成最小2的n次冪
// 直接將該值賦給新的容量 else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
// 無參構造建立的map,給出默認容量和threshold 16, 16*0.75 else {
// zero initial threshold signifies using
defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 新的threshold = 新的cap * 0.75
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft <
(float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
// 計算出新的數組長度後賦給當前成員變量table
@SuppressWarnings({
"rawtypes","unchecked"
}
)
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//新建hash桶數組
table = newTab;
//將新數組的值複製給舊的hash桶數組
// 若是原先的數組沒有初始化,那麼resize的初始化工做到此結束,不然進入擴容元素重排邏輯,使
其均勻的分散
if (oldTab != null) {
// 遍歷新數組的全部桶下標
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
// 舊數組的桶下標賦給臨時變量e,而且解除舊數組中的引用,不然就數組無
法被GC回收
oldTab[j] = null;
// 若是e.next==null,表明桶中就一個元素,不存在鏈表或者紅黑樹
if (e.next == null)
// 用一樣的hash映射算法把該元素加入新的數組
newTab[e.hash & (newCap - 1)] = e;
// 若是e是TreeNode而且e.next!=null,那麼處理樹中元素的重排 else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
// e是鏈表的頭而且e.next!=null,那麼處理鏈表中元素重排 else {
// preserve order
// loHead,loTail 表明擴容後不用變換下標,見注1
Node<K,V> loHead = null, loTail = null;
// hiHead,hiTail 表明擴容後變換下標,見注1
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
// 遍歷鏈表
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
// 初始化head指向鏈表當前元素e,e不必定是鏈表的
第一個元素,初始化後loHead
// 表明下標保持不變的鏈表的頭元素
loHead = e; else
// loTail.next指向當前e
loTail.next = e;
// loTail指向當前的元素e
// 初始化後,loTail和loHead指向相同的內存,因此當
loTail.next指向下一個元素時,
// 底層數組中的元素的next引用也相應發生變化,形成lowHead.next.next.....
// 跟隨loTail同步,使得lowHead能夠連接到全部屬於該鏈
表的元素。
loTail = e;
} else {
if (hiTail == null)
// 初始化head指向鏈表當前元素e, 初始化後hiHead
表明下標更改的鏈表頭元素
hiHead = e; else
hiTail.next = e;
hiTail = e;
}
}
while ((e = next) != null);
// 遍歷結束, 將tail指向null,並把鏈表頭放入新數組的相應下標,
造成新的映射。
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
34. HashMap是怎麼解決哈希衝突的?
- 答:在解決這個問題以前,咱們首先須要知道什麼是哈希衝突,而在瞭解哈希衝突以前咱們還要知道什麼是哈希才行;
35. 可否使用任何類做爲 Map 的 key?
可使用任何類做爲 Map 的 key,然而在使用以前,須要考慮如下幾點:
- 若是類重寫了 equals() 方法,也應該重寫 hashCode() 方法。
- 類的全部實例須要遵循與 equals() 和 hashCode() 相關的規則。
- 若是一個類沒有使用 equals(),不該該在 hashCode() 中使用它。
- 用戶自定義 Key 類最佳實踐是使之爲不可變的,這樣 hashCode() 值能夠被緩存起來,擁有更好的性能。不可變的類也能夠確保 hashCode() 和 equals() 在將來不會改變,這樣就會解決與可變相關的問題了。
36. 爲何HashMap中String、Integer這樣的包裝類適合做爲K?
37. 若是使用Object做爲HashMap的Key,應該怎麼辦呢?
38. HashMap爲何不直接使用hashCode()處理後的哈希值直接做爲table的下標?
39. HashMap 的長度爲何是2的冪次方
40. HashMap 與 HashTable 有什麼區別?
- 線程安全: HashMap 是非線程安全的,HashTable 是線程安全的;HashTable 內部的方法基本都通過 synchronized 修飾。(若是你要保證線程安全的話就使用 ConcurrentHashMap );
- 效率: 由於線程安全的問題,HashMap 要比 HashTable 效率高一點。另外,HashTable 基本被淘汰,不要在代碼中使用它;(若是你要保證線程安全的話就使用 ConcurrentHashMap );
- 對Null key 和Null value的支持: HashMap 中,null 能夠做爲鍵,這樣的鍵只有一個,能夠有一個或多個鍵所對應的值爲 null。可是在 HashTable 中 put 進的鍵值只要有一個 null,直接拋NullPointerException。
- 初始容量大小和每次擴充容量大小的不一樣 :
- 建立時若是不指定容量初始值,Hashtable 默認的初始大小爲11,以後每次擴充,容量變爲原來的2n+1。HashMap 默認的初始化大小爲16。以後每次擴充,容量變爲原來的2倍。
- 建立時若是給定了容量初始值,那麼 Hashtable 會直接使用你給定的大小,而 HashMap 會將其擴充爲2的冪次方大小。也就是說 HashMap 老是使用2的冪做爲哈希表的大小,後面會介紹到爲何是2的冪次方。
- 底層數據結構: JDK1.8 之後的 HashMap 在解決哈希衝突時有了較大的變化,當鏈表長度大於閾值(默認爲8)時,將鏈表轉化爲紅黑樹,以減小搜索時間。Hashtable 沒有這樣的機制。
- 推薦使用:在 Hashtable 的類註釋能夠看到,Hashtable 是保留類不建議使用,推薦在單線程環境下使用 HashMap 替代,若是須要多線程使用則用 ConcurrentHashMap 替代。
41. 什麼是TreeMap 簡介
- TreeMap 是一個有序的key-value集合,它是經過紅黑樹實現的。
- TreeMap基於紅黑樹(Red-Black tree)實現。該映射根據其鍵的天然順序進行排序,或者根據建立映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。
- TreeMap是線程非同步的。
42. 如何決定使用 HashMap 仍是 TreeMap?
- 對於在Map中插入、刪除和定位元素這類操做,HashMap是最好的選擇。然而,假如你須要對一個有序的key集合進行遍歷,TreeMap是更好的選擇。基於你的collection的大小,也許向HashMap中添加元素會更快,將map換爲TreeMap進行有序key的遍歷。
43. HashMap 和 ConcurrentHashMap 的區別
- ConcurrentHashMap對整個桶數組進行了分割分段(Segment),而後在每個分段上都用lock鎖進行保護,相對於HashTable的synchronized鎖的粒度更精細了一些,併發性能更好,而HashMap沒有鎖機制,不是線程安全的。(JDK1.8以後ConcurrentHashMap啓用了一種全新的方式實現,利用CAS算法。)
- HashMap的鍵值對容許有null,可是ConCurrentHashMap都不容許。
44. ConcurrentHashMap 和 Hashtable 的區別?
三、JDK1.8的ConcurrentHashMap(TreeBin: 紅黑二叉樹節點 Node: 鏈表節點):
45. ConcurrentHashMap 底層具體實現知道嗎?實現原理是什麼?
- 若是相應位置的Node不爲空,且當前該節點不處於移動狀態,則對該節點加synchronized鎖,若是該節點的hash不小於0,則遍歷鏈表更新節點或插入新節點;
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key, value, null);
break;
}
}
}
- 若是該節點是TreeBin類型的節點,說明是紅黑樹結構,則經過putTreeVal方法往紅黑樹中插入節點;若是binCount不爲0,說明put操做對數據產生了影響,若是當前鏈表的個數達到8個,則經過treeifyBin方法轉化爲紅黑樹,若是oldVal不爲空,說明是一次更新操做,沒有對元素個數產生影響,則直接返回舊值;
- 若是插入的是一個新節點,則執行addCount()方法嘗試更新元素個數baseCount;
4、輔助工具類
46. Array 和 ArrayList 有何區別?
- Array 能夠存儲基本數據類型和對象,ArrayList 只能存儲對象。
- Array 是指定固定大小的,而 ArrayList 大小是自動擴展的。
- Array 內置方法沒有 ArrayList 多,好比 addAll、removeAll、iteration 等方法只有 ArrayList有。
47. 如何實現 Array 和 List 之間的轉換?
- Array 轉 List: Arrays. asList(array) ;
- List 轉 Array:List 的 toArray() 方法。
48. comparable 和 comparator的區別?
- comparable接口其實是出自java.lang包,它有一個 compareTo(Object obj)方法用來排序
- comparator接口其實是出自 java.util 包,它有一個compare(Object obj1, Object obj2)方法用來排序
- 通常咱們須要對一個集合使用自定義排序時,咱們就要重寫compareTo方法或compare方法,當咱們須要對某一個集合實現兩種排序方式,好比一個song對象中的歌名和歌手名分別採用一種排序方法的話,咱們能夠重寫compareTo方法和使用自制的Comparator方法或者以兩個Comparator來實現歌名排序和歌星名排序,第二種表明咱們只能使用兩個參數版的Collections.sort().
49. Collection 和 Collections 有什麼區別?
- java.util.Collection 是一個集合接口(集合類的一個頂級接口)。它提供了對集合對象進行基本操做的通用接口方法。Collection接口在Java 類庫中有不少具體的實現。Collection接口的意義是爲各類具體的集合提供了最大化的統一操做方式,其直接繼承接口有List與Set。
- Collections則是集合類的一個工具類/幫助類,其中提供了一系列靜態方法,用於對集合中元素進行排序、搜索以及線程安全等各類操做。
50. TreeMap 和 TreeSet 在排序時如何比較元素?Collections 工具類中的 sort()方法如何比較元素?
51. Collection 和 Collections 有什麼區別?
- java.util.Collection 是一個集合接口(集合類的一個頂級接口)。它提供了對集合對象進行基本操做的通用接口方法。Collection接口在Java 類庫中有不少具體的實現。Collection接口的意義是爲各類具體的集合提供了最大化的統一操做方式,其直接繼承接口有List與Set。
- Collections則是集合類的一個工具類/幫助類,其中提供了一系列靜態方法,用於對集合中元素進行排序、搜索以及線程安全等各類操做。