集合知識點總結node
Java的集合類主要由兩個接口派生而出:Collection和Map 算法
Set、List和Map能夠看作集合的三大類:數組
List集合是有序集合,集合中的元素能夠重複,訪問集合中的元素能夠根據元素的索引來訪問。緩存
Set集合是無序集合,集合中的元素不能夠重複,訪問集合中的元素只能根據元素自己來訪問(也是集合裏元素不容許重複的緣由)。安全
Map集合中保存Key-value對形式的元素,訪問時只能根據每項元素的key來訪問其value。數據結構
ArrayList併發
有序可重複容許爲空,初始容量爲10的動態數組(非安全)app
ArrayList類中兩個私有屬性,elementData存儲ArrayList內的元素,size表示它包含的元素的數量。dom
有序:元素是按照該 collection 的迭代器返回它們的順序排列的。函數
動態擴容:int newCapacity = (oldCapacity * 3)/2 + 1;
插入元素:按照指定位置,把從指定位置開始的全部元素利用System.arraycopy方法作一個總體的複製,向後移動一個位置(固然先要用ensureCapacity方法進行判斷,加了一個元素以後數組會不會不夠 大),而後指定位置的元素設置爲須要插入的元素,完成了一次插入的操做,該方法的根本目的就是將index位置空出來以供新數據插入,這裏須要進行數組數據的右移,若是要複製的元素不少, 那麼就會比較耗費性能。
刪除元素:一、把指定元素後面位置的全部元素,利用System.arraycopy方法總體向前移動一個位置
二、最後一個位置的元素指定爲null,這樣讓gc能夠去回收它,若是要複製的元素不少,那麼就會比較耗費性能。
訪問元素:ArrayList底層以數組實現,是一種隨機訪問模式,再加上它實現了RandomAccess接口,所以查 找也就是get的時候很是快。基於數組實現,能夠經過下標索引直接查找到指定位置的元素,所以 查找效率高
ArrayList是線程非安全的,一個方法是用Collections.synchronizedList方法把你的ArrayList變成一個線程安全的List,另外一個方法就是Vector,它是ArrayList的線程安全版本
LinkedList
有序可重複容許爲空,初始容量爲10 的雙向鏈表(非安全)
LinkedList中定義了兩個私有屬性:size 和Entry (Entry中包含成員變量:previous, next,element)
LinkedList底層的數據結構是基於雙向循環鏈表的,且頭結點中不存放數據,數據結構——咱們能夠稱之爲節點,節點實例保存業務數據、前一個節點的位置信息和後一個節點位置信息
插入元素:改變先後Entry的引用地址
刪除元素:預刪除節點的前一節點的後指針指向預刪除節點的後一個節點。預刪除節點的後一節點的前指針指向預刪除節點的前一個節點。清空預刪除節點:交給gc完成資源回收,刪除操做結束。與 ArrayList比較而言,LinkedList的刪除動做不須要「移動」不少數據,從而效率更高。
訪問元素:get(int)方法首先判斷位置信息是否合法(大於等於0,小於當前LinkedList實例的Size),而後遍歷到具體位置,得到節點的業務數據(element)並返回。當index小於數組大小的一半的時候 (size >> 1表示size / 2,使用移位運算提高代碼運行效率),從前向後查找;不然,從後向前查找。
ArrayList和LinkedList比較
(1)LinkedList作插入、刪除的時候,慢在尋址,快在只須要改變先後Entry的引用地址
(2)ArrayList作插入、刪除的時候,慢在數組元素的批量copy,快在尋址
HashMap
無序可重複容許爲空,鏈表的數組 (非安全)
構造一個具備默認初始容量 (16) 和默認加載因子 (0.75) 的空 HashMap。
無序:特別說明這個無序指的是遍歷HashMap的時候,獲得的元素的順順序
可重複:Key重複會覆蓋、Value容許重複)
Key和Value都容許爲空
非線程安全的
HashMap的底層主要是基於數組和鏈表來實現的,它之因此有至關快的查詢速度主要是由於它是經過計算散列碼來決定存儲的位置。(key 的hash值決定位置)HashMap中主要是經過key的hashCode來計算hash值的,只要hashCode相同,計算出來的hash值就同樣。
若是存儲的對象對多了,就有可能不一樣的對象所算出來的hash值是相同的,這就出現了所謂的hash衝突。學過數據結構的同窗都知道,解決hash衝突的方法有不少,HashMap底層是經過鏈表來解決hash衝突的。Entry就是數組中的元素,每一個 Map.Entry 其實就是一個key-value對,它持有一個指向下一個元素的引用,這就構成了鏈表。(previous element next)
構造一個帶指定初始容量和默認加載因子 (0.75) 的空 HashMap: 空間利用和查找效率最好,加載因子它衡量的是一個散列表的空間的使用程度,負載因子越大表示散列表的裝填程度越高,反之愈小。;若是負載因子過小,那麼散列表的數據將過於稀疏,對空間形成嚴重浪費 ; 加載因子越大,填滿的元素越多,好處是,空間利用率高了,但:衝突的機會加大了.鏈表長度會愈來愈長,查找效率下降。
存儲元素(讀取):先判斷key是否爲null,若爲null,則直接調用putForNullKey方法,將value放置在數組第一個位置上。若不爲空則根據key的hashCode從新計算hash值,而後根據hash值獲得這個元素在table數組中的位置(即下標),若是table數組在該位置處已經存放有其餘元素了,則經過比較是否存在相同的key,若存在則覆蓋原來key的value,不然將該元素保存在鏈頭(最早保存的元素放在鏈尾)。若table在該處沒有元素,就直接將該元素放到此數組中的該位置上。經過比較是否存在相同的key,若存在則覆蓋原來key的value:對比Key是否相同,是先比HashCode是否相同,HashCode相同再判斷equals是否爲true
訪問元素:此時的 HashMap 具備最好的性能:當程序經過 key 取出對應 value 時,系統只要先計算出該key 的 hashCode() 返回值,在根據該 hashCode 返回值找出該 key 在 table 數組中的索引,而後
取出該索引處的 Entry,最後返回該 key 對應的 value 便可
2的n 次方:
HashMap的table而言,數據分佈須要均勻(最好每項都只有一個元素,這樣就能夠直接找到),不能太緊也不能太鬆,太緊會致使查詢速度慢,太鬆則浪費空間。HashMap的底層數組長度老是2的n次方數據在table數組中分佈較均勻,查詢速度也較快。
LinkedHashMap
HashMap+LinkedList,即它既使用HashMap操做數據結構,又使用LinkedList維護插入元素的前後順序有序可重複
HashMap迭代HashMap的順序並非HashMap放置的順序,也就是無序。咱們期待一個有序的Map。LinkedHashMap就閃亮登場了,它雖然增長了時間和空間上的開銷,可是經過維護一個運行於全部條目的雙向鏈表,LinkedHashMap保證了元素迭代的順序,該迭代順序能夠是插入順序或者是訪問順序。
利用LinkedHashMap實現LRU算法緩存
LRU:LRU即Least Recently Used,最近最少使用,也就是說,當緩存滿了,會優先淘汰那些最近最不常訪問的數據。比
LinkedHashMap能夠實現LRU算法的緩存基於兩點:
一、LinkedList首先它是一個Map,Map是基於K-V的,和緩存一致
二、LinkedList提供了一個boolean值可讓用戶指定是否實現LRU
accessOrder 1)false,全部的Entry按照插入的順序排列(2)true,全部的Entry按照訪問的順序排列
concurrentHashMap
背景: 線程不安全的HashMap 使用Hashmap進行put操做會引發死循環 ;
效率低下的HashTable容器 , 當一個線程訪問HashTable的同步方法時,其餘線程訪問HashTable的同步方法時,可能會進入阻塞或輪詢狀態。
ConcurrentHashMap的鎖分段技術(數據分段存儲,分段枷鎖)
首先將數據分紅一段一段的存儲,而後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其餘段的數據也能被其餘線程訪問。
ConcurrentHashMap爲了提升自己的併發能力,在內部採用了一個叫作Segment的結構,一個Segment其實就是一個類Hash Table的結構,Segment內部維護了一個鏈表數組。當對HashEntry數組的數據進行修改時,必須首先得到它對應的Segment鎖。
高併發:ConcurrentHashMap定位一個元素的過程須要進行兩次Hash操做,第一次Hash定位到Segment,第二次Hash定位到元素所在的鏈表的頭部。在最理想的狀況下,ConcurrentHashMap能夠最高同時支持Segment數量大小的寫操做(恰好這些寫操做都很是平均地分佈在全部的Segment上),因此,經過這一種結構,ConcurrentHashMap的併發能力能夠大大的提升。總的Map包含了16個Segment(默認數量),每一個Segment內部包含16個HashEntry(默認數量),這樣對於這個key所在的Segment加鎖的同時,其餘15個Segmeng還能正常使用,在性能上有了大大的提高。
詳細解釋一下Segment裏面的成員變量的意義:
count:Segment中元素的數量
modCount:對table的大小形成影響的操做的數量(好比put或者remove操做)
threshold:閾值,Segment裏面元素的數量超過這個值依舊就會對Segment進行擴容
table:鏈表數組,數組中的每個元素表明了一個鏈表的頭部
loadFactor:負載因子,用於肯定threshold
volatile的保證:對volatile域的寫入操做happens-before於每個後續對同一個域的讀寫操做。因此,每次判斷count變量的時候,即便剛好其餘線程改變了segment也會體現出來。
get方法沒有使用鎖來同步,只是判斷獲取的entry的value是否爲null,爲null時才使用加鎖的方式再次去獲取。
put 操做:首先對Segment的put操做是加鎖完成的。由於每一個HashEntry中的next也是final的,無法對鏈表最後一個元素增長一個後續entry因此新增一個entry的實現方式只能經過頭結點來插入了。
remove 操做:先定位Segment的過程,而後肯定須要刪除的元素的位置, 程序就將待刪除元素前面的那一些元
素所有複製一遍,而後再一個一個從新接到鏈表上去,
知識問答:
1ArrayList,LinkedList,HashMap,LinkedHashMap,ConcurrentHashMap的底層實現原理
HashMap:HashMap底層就是一個數組結構,數組中的每一項又是一個鏈表。
LinkedHashMap:LinkedHashMap繼承於HashMap,底層使用哈希表和雙向鏈表來保存全部元素,而且它是非同步,容許使用null值和null鍵。
ConcurrentHashMap:ConcurrentHashMap採用 分段鎖的機制,實現併發的更新操做,底層採用數組+鏈表的存儲結構。在JDK1.8利用CAS+Synchronized來保證併發更新的安全,底層採用數組+鏈表+紅黑樹的存儲結構。
ArrayList:底層使用數組實現
LinkedList:底層的數據結構是基於雙向鏈表
2 1.7和1.8版本中HashMap的區別
JDK1.7中
使用一個Entry數組來存儲數據,用key的hashcode取模來決定key會被放到數組裏的位置,若是hashcode相同,或者hashcode取模後的結果相同(hash collision),那麼這些key會被定位到Entry數組的同一個格子裏,這些key會造成一個鏈表。在hashcode特別差的狀況下,比方說全部key的hashcode都相同,這個鏈表可能會很長,那麼put/get操做均可能須要遍歷這個鏈表也就是說時間複雜度在最差狀況下會退化到O(n)
JDK1.8中
使用一個Node數組來存儲數據,但這個Node多是鏈表結構,也多是紅黑樹結構
若是插入的key的hashcode相同,那麼這些key也會被定位到Node數組的同一個格子裏。
若是同一個格子裏的key不超過8個,使用鏈表結構存儲。
若是超過了8個,那麼會調用treeifyBin函數,將鏈表轉換爲紅黑樹。
那麼即便hashcode徹底相同,因爲紅黑樹的特色,查找某個特定元素,也只須要O(log n)的開銷
也就是說put/get的操做的時間複雜度最差只有O(log n)
3 1.7和1.8版本中ConcurrentHashMap的區別
1.7中concurrenthashmap主要使用Segment來減少鎖力度,把hashmap分割成若干個segment,在put的時候須要鎖住segment,get時候不加鎖,使用volatile來保證可見性,當要統計全局時(size),首先會嘗試屢次計算modcount來肯定,這幾回嘗試中,是否有其餘線程進行了修改操做,若是沒有,則直接返回size,若是有,則須要一次鎖住全部的segment來計算。
1.7中concurrenthashmap中,當長度過長時碰撞會很頻繁,鏈表的增刪改查操做都會消耗很長的時間,因此1.8中徹底重寫了,主要有幾點東西不一樣:
1 不採用segment而採用node,鎖住node來實現減少鎖力度
2 設計了moved狀態,當resize的過程當中,線程2還在put數據,線程2會幫助resize
3 使用三個cas操做來確保node的一些操做的原子性,這種方式代替了鎖
4 sizeCtl的不一樣值來表明不一樣含義,起到了控制的做用
4 HashMap能不能排序?HashMap的長度爲何要是2的次方?
2的N次方
HashMap爲了存取高效,要儘可能較少碰撞,就是要儘可能把數據分配均勻,每一個鏈表長度大體相同,這個實現就在把數據存到哪一個鏈表中的算法;
這個算法實際就是取模,hash%length,計算機中直接求餘效率不如位移運算,源碼中作了優化hash&(length-1),hash%length==hash&(length-1)的前提是length是2的n次方;