Java集合常見知識點

Java集合的框架有哪幾種

 兩種map和collection,其中collection分爲set和list.

Vector, ArrayList和LinkedList的區別

  • ArrayList的實現是一個可變數組,默認的初始長度爲10,也可以我們自己設置容量,但是沒有設置的時候默認的是空數組,只有在第一步add的時候會進行擴容至10(重新創建了數組),後面按照3/2的大小進行擴容,使線程不安全的,使用多讀取、少插入的情況。
  • LinkedList是基於雙向鏈表的實現,使用了尾插法的方式,內部維護了鏈表的長度,以及頭節點和尾節點,所以獲取長度不需要遍歷,適合一些插入/刪除頻繁的情況。
  • Vector是線程安全的,實現方式和ArrayList相似,也是基於數組,但是方法上面有synchronized關鍵詞修飾,其擴容方式是原來的兩倍。

HashMap, HashTable和ConcurrentHashMap的區別

  • HashMap是Map類型的一種最常用的數據結構,其底部實現是數組+鏈表(在1.8版本後變成了數組+鏈表/紅黑樹的方法),其key可以是null,默認hash值爲0,以2的冪等次進行擴容,時線程不安全的。
  • HashTable的實現形式和HashMap差不多,它是線程安全的,繼承了Dictionary,也是key-value的模式,但是其key不能爲null。
  • ConcurrentHashMap是JUC併發包中的一種,在HashMap的基礎上進行了修改,因爲HashMap其實是線程不安全的,而HashTable雖然是線程安全的,但是HashTable是全程加鎖的,因而性能不好(是Java遺留類),ConcurrentHashMap採用分段的思想,把原本一個數組分成默認的16段,就可以最多容納16個線程併發操作,着16個段叫做Segment,是基於ReentrantLock來實現的。

談談HashMap

HashMap是Map結構,內部使用了數組+鏈表的方式,在1.8後,當鏈表長度達到8的時候,會變成紅黑樹,這樣就可以把查詢的複雜度變成O(nlogn),默認負載因子爲0.75。(負載因子爲0.75的原因:當負載因子太小時,很容易觸發擴容;負載因子太大則容易出現碰撞,所以這是一個空間和時間的均衡點,在1.8的HashMap介紹中提到,0.75的負載因子能讓隨機hash更加滿足0.5的泊松分佈)

此外,1.7的時候時頭插法,1.8後變成了尾插法,主要是爲了解決rehash出現的死循環問題,而且1.7的時候時先擴容再插入,1.8則是先插入再擴容(原因:因爲讀寫問題,HashMap並不是線程安全的,如果先擴容再寫入,那在擴容期間,是訪問不到新放入的值的,因此先插入,這樣在擴容期間新插入的值也是存在的)

1.7版本使用了9次擾動,5次異或,4次位移,減少hash衝突,但是1.8只使用了一次異或,一次位移。

談談ConcurrentHashMap

concurrentHashMap是線程安全的map結構,它的核心思想是分段鎖。在1.7版本的時候,內部維護了segment數組,默認是16個,segment中有一個Table(相當於一個segment存放一個HashMap),segment鎖繼承了ReentrantLock,使用了互斥鎖,ConcurrentHashMap的size其實就是segment數組的count和。而在1.8的時候做了一個大改版,廢除了segment,採用了CAS加synchronized方式進行分段鎖(還有自旋鎖的保證),而且節點對象改用了Node替換HashEntry。

Node可以支持鏈表和紅黑樹的轉化,比如TreeBin就是繼承了Node,這樣子可以直接用instanceof來區分。1.8的put就很複雜,會先計算hash值,然後根據hash值選出Node數組的下標(默認數組是空,所以一開始put的時候會初始化,指定負載因子是0.75,不可變),判斷是否爲空,如果爲空,則用cas操作來賦值首節點,如果失敗,則因爲自旋,會進入非空節點的邏輯,這個時候會用synvhronized加鎖頭節點(保證整條鏈路鎖定)這個時候還會進行二次判斷,是否是同一個首節點,再分首節點是鏈表還是樹結構,進行遍歷判斷。

ConcurrentHashMap的擴容方式

1.7版本的ConcurrentHashMap的基於segment的,segment內部維護了HashEntry數組,所以擴容是在這個基礎上的,類比HashMap的擴容。

1.8版本的ConcurrentHashMap擴容方式比較複雜,利用了ForwardingNode,會先根據機器內核數來分配每個線程能分到的busket數(最小是16),這樣可以做到多攜程協助遷移,提升速遞,然後更具自己分配的busket數來進行節點轉移,如果爲空,就放置ForeardingNode,代表已經遷移完成,如果是非空節點,加鎖,鏈路循環,進行遷移。

HashMap的put方法過程

判斷key是否是null,如果是null對應的hash值是0,獲得hash值過後則進行擾動,(1.7是9次擾動,5次異或,4次位移;1.8是2次擾動,一次異或,一次位移),獲得的新hash值找出所在的index,(n-1)&hash,根據下表找到對應的Node/Entry,然後遍歷表/紅黑樹,如果遇到hash值相同且equals相同,則覆蓋值,如果不是則新增。如果節點數大於8了,則進行數化(1.8)。完成後,判斷當前的長度是否大於閾值,是就擴容(先插入再擴容是1.8的操作,而1.7是先擴容再put)。

爲什麼修改hashcode必須要修改equals

與map有關,在map中判斷是否是同一個對象時,會先判斷hash值,再判斷equals,如果只重寫了hashcode而沒有重寫equals,而是使用Object的equals,那麼就是判斷兩者指針是否一致,則會出現兩個值相同對象對於map而言是兩個不同對象從而出現問題。

談談TreeMap

TreeMap是map中的一種特殊的map,我們知道map基本是無序的,但是TreeMap是會自動進行排序的,也就是一個有序的Map(使用紅黑樹來實現),如果設置了Comparator比較器,則會根據比較器來比較兩者的大小,如果沒有則key需要是Comparable的子類(代碼沒有實現檢查,會直接拋出轉化異常)

談談LinkedHashMap

LinkedHashMap是HashMap的一種特殊分支,是某種有序的HashMap,和TreeMap不一的是,LinkedHashMap採用HashMap+雙向鏈表的方式來構造,有兩種有序模式:訪問有序和插入有序。當accessOrder設置爲false,表示不是訪問有序而是插入有序,這也是默認值,表明LinkedHashMap中存儲的順序是按照調用put方式插入的順序進行排序的;當accessOrder設置爲true時,每次get的時候都會把鏈表的節點移除掉,放到鏈表的最後面,這樣就是LRU的一種實現方式。

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章