常見容器主要包括 Collection 和 Map 兩種,Collection 存儲着對象的集合,而 Map 存儲着鍵值對(兩個對象)的映射表。java
Collectionnode
Set算法
1.SetTreeSet:基於紅黑樹實現,支持有序性操做,例如:根據一個範圍查找元素的操做。可是查找效率不如 HashSet,HashSet 查找的時間複雜度爲 O(1),TreeSet 則爲 O(logN)。編程
2.HashSet:基於哈希表實現,支持快速查找,但不支持有序性操做。而且失去了元素的插入順序信息,也就是說使用 Iterator 遍歷 HashSet 獲得的結果是不肯定的。設計模式
3.LinkedHashSet:具備 HashSet 的查找效率,且內部使用雙向鏈表維護元素的插入順序。數組
Queue安全
Map數據結構
二、ArrayList 和 LinkedList 的區別?多線程
ArrayList:底層是基於數組實現的,查找快,增刪較慢;併發
LinkedList:底層是基於鏈表實現的。確切的說是循環雙向鏈表(JDK1.6 以前是雙向循環鏈表、JDK1.7 以後取消了循環),查找慢、增刪快。LinkedList 鏈表由一系列表項鍊接而成,一個表項包含 3 個部分:元素內容、前驅表和後驅表。鏈表內部有一個 header 表項,既是鏈表的開始也是鏈表的結尾。header 的後繼表項是鏈表中的第一個元素,header 的前驅表項是鏈表中的最後一個元素。
補充:
ArrayList 的增刪未必就是比 LinkedList 要慢:
1. 若是增刪都是在末尾來操做【每次調用的都是 remove() 和 add()】,此時 ArrayList 就不須要移動和複製數組來進行操做了。若是數據量有百萬級的時,速度是會比 LinkedList 要快的。
2. 若是刪除操做的位置是在中間。因爲 LinkedList 的消耗主要是在遍歷上,ArrayList 的消耗主要是在移動和複製上(底層調用的是 arrayCopy() 方法,是 native 方法)。LinkedList 的遍歷速度是要慢於 ArrayList 的複製移動速度的若是數據量有百萬級的時,仍是 ArrayList 要快。
三、ArrayList 實現 RandomAccess 接口有何做用?爲什麼 LinkedList 卻沒實現這個接口?1. RandomAccess 接口只是一個標誌接口,只要 List 集合實現這個接口,就能支持快速隨機訪問。經過查看 Collections 類中的 binarySearch() 方法,能夠看出,判斷 List 是否實現 RandomAccess 接口來實行indexedBinarySerach(list, key) 或 iteratorBinarySerach(list, key)方法。再經過查看這兩個方法的源碼發現:實現 RandomAccess 接口的 List 集合採用通常的 for 循環遍歷,而未實現這接口則採用迭代器,即 ArrayList 通常採用 for 循環遍歷,而 LinkedList 通常採用迭代器遍歷;2. ArrayList 用 for 循環遍歷比 iterator 迭代器遍歷快,LinkedList 用 iterator 迭代器遍歷比 for 循環遍歷快。因此說,當咱們在作項目時,應該考慮到 List 集合的不一樣子類採用不一樣的遍歷方式,可以提升性能。
四、ArrayList 的擴容機制?
推薦閱讀:
https://juejin.im/post/5d42ab5e5188255d691bc8d6
五、Array 和 ArrayList 有何區別?何時更適合用 Array?
1.Array 能夠容納基本類型和對象,而 ArrayList 只能容納對象;
2.Array 是指定大小的,而 ArrayList 大小是固定的。
何時更適合使用 Array:
1.若是列表的大小已經指定,大部分狀況下是存儲和遍歷它們;
2.對於遍歷基本數據類型,儘管 Collections 使用自動裝箱來減輕編碼任務,在指定大小的基本類型的列表上工做也會變得很慢;
3.若是你要使用多維數組,使用 [ ][ ] 比 List> 更容易。
六、HashMap 的實現原理/底層數據結構?JDK1.7 和 JDK1.8
JDK1.7:Entry數組 + 鏈表
JDK1.8:Node 數組 + 鏈表/紅黑樹,當鏈表上的元素個數超過 8 個而且數組長度 >= 64 時自動轉化成紅黑樹,節點變成樹節點,以提升搜索效率和插入效率到 O(logN)。Entry 和 Node 都包含 key、value、hash、next 屬性。
七、HashMap 的 put 方法的執行過程?
當咱們想往一個 HashMap 中添加一對 key-value 時,系統首先會計算 key 的 hash 值,而後根據 hash 值確認在 table 中存儲的位置。若該位置沒有元素,則直接插入。不然迭代該處元素鏈表並依次比較其 key 的 hash 值。若是兩個 hash 值相等且 key 值相等(e.hash == hash && ((k = e.key) == key || key.equals(k))),則用新的 Entry 的 value 覆蓋原來節點的 value。若是兩個 hash 值相等但 key 值不等 ,則將該節點插入該鏈表的鏈頭。
八、HashMap 的 get 方法的執行過程?
經過 key 的 hash 值找到在 table 數組中的索引處的 Entry,而後返回該 key 對應的 value 便可。
在這裏可以根據 key 快速的取到 value 除了和 HashMap 的數據結構密不可分外,還和 Entry 有莫大的關係。HashMap 在存儲過程當中並無將 key,value 分開來存儲,而是當作一個總體 key-value 來處理的,這個總體就是Entry 對象。同時 value 也只至關於 key 的附屬而已。在存儲的過程當中,系統根據 key 的 HashCode 來決定 Entry 在 table 數組中的存儲位置,在取的過程當中一樣根據 key 的 HashCode 取出相對應的 Entry 對象(value 就包含在裏面)。
九、HashMap 的 resize 方法的執行過程?
有兩種狀況會調用 resize 方法:
1. 第一次調用 HashMap 的 put 方法時,會調用 resize 方法對 table 數組進行初始化,若是不傳入指定值,默認大小爲 16。
2. 擴容時會調用 resize,即 size > threshold 時,table 數組大小翻倍。
每次擴容以後容量都是翻倍。擴容後要將原數組中的全部元素找到在新數組中合適的位置。
當咱們把 table[i] 位置的全部 Node 遷移到 newtab 中去的時候:這裏面的 node 要麼在 newtab 的 i 位置(不變),要麼在 newtab 的 i + n 位置。也就是咱們能夠這樣處理:把 table[i] 這個桶中的 node 拆分爲兩個鏈表 l1 和 l2:若是 hash & n == 0,那麼當前這個 node 被鏈接到 l1 鏈表;不然鏈接到 l2 鏈表。這樣下來,當遍歷完 table[i] 處的全部 node 的時候,咱們獲得兩個鏈表 l1 和 l2,這時咱們令 newtab[i] = l1,newtab[i + n] = l2,這就完成了 table[i] 位置全部 node 的遷移(rehash),這也是 HashMap 中容量必定的是 2 的整數次冪帶來的方便之處。
十、HashMap 的 size 爲何必須是 2 的整數次方?
1.這樣作老是可以保證 HashMap 的底層數組長度爲 2 的 n 次方。當 length 爲 2 的 n 次方時,h & (length - 1) 就至關於對 length 取模,並且速度比直接取模快得多,這是 HashMap 在速度上的一個優化。並且每次擴容時都是翻倍。
2.若是 length 爲 2 的次冪,則 length - 1 轉化爲二進制一定是 11111……的形式,在與 h 的二進制進行與操做時效率會很是的快,並且空間不浪費。可是,若是 length 不是 2 的次冪,好比:length 爲 15,則 length - 1 爲 14,對應的二進制爲 1110,在於 h 與操做,最後一位都爲 0 ,而 0001,0011,0101,1001,1011,0111,1101 這幾個位置永遠都不能存放元素了,空間浪費至關大,更糟的是這種狀況中,數組可使用的位置比數組長度小了不少,這意味着進一步增長了碰撞的概率,減慢了查詢的效率,這樣就會形成空間的浪費。
十一、HashMap 多線程死循環問題?
詳細請閱讀:
https://blog.csdn.net/xuefeng0707/article/details/40797085
https://blog.csdn.net/dgutliangxuan/article/details/78779448
主要是多線程同時 put 時,若是同時觸發了 rehash 操做,會致使 HashMap 中的鏈表中出現循環節點,進而使得後面 get 的時候,會死循環。
十二、HashMap 的 get 方法可否判斷某個元素是否在 map 中?
HashMap 的 get 函數的返回值不能判斷一個 key 是否包含在 map 中,由於 get 返回 null 有多是不包含該 key,也有可能該 key 對應的 value 爲 null。由於 HashMap 中容許 key 爲 null,也容許 value 爲 null。
1三、HashMap 與 HashTable 的區別是什麼?
1.HashTable 基於 Dictionary 類,而 HashMap 是基於 AbstractMap。Dictionary 是任何可將鍵映射到相應值的類的抽象父類,而 AbstractMap 是基於 Map 接口的實現,它以最大限度地減小實現此接口所需的工做。
2.HashMap 的 key 和 value 都容許爲 null,而 Hashtable 的 key 和 value 都不容許爲 null。HashMap 遇到 key 爲 null 的時候,調用 putForNullKey 方法進行處理,而對 value 沒有處理;Hashtable 遇到 null,直接返回 NullPointerException。
3.Hashtable 是線程安全的,而 HashMap 不是線程安全的,可是咱們也能夠經過 Collections.synchronizedMap(hashMap),使其實現同步。
HashTable的補充:
HashTable 和 HashMap 的實現原理幾乎同樣,差異無非是
1. HashTable 不容許 key 和 value 爲 null;
2. HashTable 是線程安全的。可是 HashTable 線程安全的策略實現代價卻太大了,簡單粗暴,get/put 全部相關操做都是 synchronized 的,這至關於給整個哈希表加了一把大鎖,多線程訪問時候,只要有一個線程訪問或操做該對象,那其餘線程只能阻塞,至關於將全部的操做串行化,在競爭激烈的併發場景中性能就會很是差。
1四、HashMap 與 ConcurrentHashMap 的區別是什麼?
HashMap 不是線程安全的,而 ConcurrentHashMap 是線程安全的。
ConcurrentHashMap 採用鎖分段技術,將整個Hash桶進行了分段segment,也就是將這個大的數組分紅了幾個小的片斷 segment,並且每一個小的片斷 segment 上面都有鎖存在,那麼在插入元素的時候就須要先找到應該插入到哪個片斷 segment,而後再在這個片斷上面進行插入,並且這裏還須要獲取 segment 鎖,這樣作明顯減少了鎖的粒度。
1六、ConcurrentHashMap 的實現原理是什麼?
數據結構
JDK 7:中 ConcurrentHashMap 採用了數組 + Segment + 分段鎖的方式實現。JDK 8:中 ConcurrentHashMap 參考了 JDK 8 HashMap 的實現,採用了數組 + 鏈表 + 紅黑樹的實現方式來設計,內部大量採用 CAS 操做。
ConcurrentHashMap 採用了很是精妙的"分段鎖"策略,ConcurrentHashMap 的主幹是個 Segment 數組。
final Segment<K,V>[] segments;
Segment 繼承了 ReentrantLock,因此它就是一種可重入鎖(ReentrantLock)。在 ConcurrentHashMap,一個 Segment 就是一個子哈希表,Segment 裏維護了一個 HashEntry 數組,併發環境下,對於不一樣 Segment 的數據進行操做是不用考慮鎖競爭的。就按默認的 ConcurrentLevel 爲 16 來說,理論上就容許 16 個線程併發執行。
因此,對於同一個 Segment 的操做才需考慮線程同步,不一樣的 Segment 則無需考慮。Segment 相似於 HashMap,一個 Segment 維護着一個HashEntry 數組:
transient volatile HashEntry<K,V>[] table;
HashEntry 是目前咱們提到的最小的邏輯處理單元了。一個 ConcurrentHashMap 維護一個 Segment 數組,一個 Segment 維護一個 HashEntry 數組。所以,ConcurrentHashMap 定位一個元素的過程須要進行兩次 Hash 操做。第一次 Hash 定位到 Segment,第二次 Hash 定位到元素所在的鏈表的頭部。
1七、HashSet 的實現原理?
HashSet 的實現是依賴於 HashMap 的,HashSet 的值都是存儲在 HashMap 中的。在 HashSet 的構造法中會初始化一個 HashMap 對象,HashSet 不容許值重複。所以,HashSet 的值是做爲 HashMap 的 key 存儲在 HashMap 中的,當存儲的值已經存在時返回 false。
1八、HashSet 怎麼保證元素不重複的?
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
元素值做爲的是 map 的 key,map 的 value 則是 PRESENT 變量,這個變量只做爲放入 map 時的一個佔位符而存在,因此沒什麼實際用處。其實,這時候答案已經出來了:HashMap 的 key 是不能重複的,而這裏HashSet 的元素又是做爲了 map 的 key,固然也不能重複了。
1九、LinkedHashMap 的實現原理?
LinkedHashMap 也是基於 HashMap 實現的,不一樣的是它定義了一個 Entry header,這個 header 不是放在 Table 裏,它是額外獨立出來的。LinkedHashMap 經過繼承 hashMap 中的 Entry,並添加兩個屬性 Entry before,after 和 header 結合起來組成一個雙向鏈表,來實現按插入順序或訪問順序排序。
LinkedHashMap 定義了排序模式 accessOrder,該屬性爲 boolean 型變量,對於訪問順序,爲 true;對於插入順序,則爲 false。通常狀況下,沒必要指定排序模式,其迭代順序即爲默認爲插入順序。
1.使用方法 iterator() 要求容器返回一個 Iterator。第一次調用 Iterator 的 next() 方法時,它返回序列的第一個元素。注意:iterator() 方法是 java.lang.Iterable 接口,被 Collection 繼承。
2.使用 next() 得到序列中的下一個元素。
3.使用 hasNext() 檢查序列中是否還有元素。
4.使用 remove() 將迭代器新返回的元素刪除。
2一、 Iterator 和 ListIterator 有什麼區別?
Iterator 可用來遍歷 Set 和 List 集合,可是 ListIterator 只能用來遍歷 List。Iterator 對集合只能是前向遍歷,ListIterator 既能夠前向也能夠後向。ListIterator 實現了 Iterator 接口,幷包含其餘的功能,好比:增長元素,替換元素,獲取前一個和後一個元素的索引等等。
2二、Iterator 和 Enumeration 接口的區別?
與 Enumeration 相比,Iterator 更加安全,由於當一個集合正在被遍歷的時候,它會阻止其它線程去修改集合。不然會拋出 ConcurrentModificationException 異常。這其實就是 fail-fast 機制。具體區別有三點:
1.Iterator 的方法名比 Enumeration 更科學;
2.Iterator 有 fail-fast 機制,比 Enumeration 更安全;
3.Iterator 可以刪除元素,Enumeration 並不能刪除元素。
2三、fail-fast 與 fail-safe 有什麼區別?
Iterator 的 fail-fast 屬性與當前的集合共同起做用,所以它不會受到集合中任何改動的影響。Java.util 包中的全部集合類都被設計爲 fail-fast 的,而 java.util.concurrent 中的集合類都爲 fail-safe 的。當檢測到正在遍歷的集合的結構被改變時,fail-fast 迭代器拋出ConcurrentModificationException,而 fail-safe 迭代器從不拋出 ConcurrentModificationException。
2四、Collection 和 Collections 有什麼區別?
Collection:是最基本的集合接口,一個 Collection 表明一組 Object,即 Collection 的元素。它的直接繼承接口有 List,Set 和 Queue。
Collections:是不屬於 Java 的集合框架的,它是集合類的一個工具類/幫助類。此類不能被實例化, 服務於 Java 的 Collection 框架。它包含有關集合操做的靜態多態方法,實現對各類集合的搜索、排序、線程安全等操做。