對於這些專題的詳解,專門作了一個983頁的PDF版本,以下
java
(更多完整項目下載。未完待續。源碼。圖文知識後續上傳github。)
能夠點擊關於我聯繫我獲取
( VX:mm14525201314)
數據結構是指相互之間存在着一種或多種關係的數據元素的集合和該集合中數據元素間的關係組成。經常使用的數據有:數組、棧、隊列、鏈表、樹、圖、堆、散列表。mysql
1)數組:在內存中連續存儲多個元素的結構。數組元素經過下標訪問,下標從0開始。優勢:訪問速度快;缺點:數組大小固定後沒法擴容,只能存儲一種類型的數據,添加刪除操做慢。適用場景:適用於需頻繁查找,對存儲空間要求不高,不多添加刪除。git
2)棧:一種特殊的線性表,只能夠在棧頂操做,先進後出,從棧頂放入元素叫入棧,從棧頂取出元素叫出棧。應用場景:用於實現遞歸功能,如斐波那契數列。github
3)隊列:一種線性表,在列表一端添加元素,另外一端取出,先進先出。使用場景:多線程阻塞隊列管理中。算法
4)鏈表:物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是經過鏈表的指針地址實現,每一個元素包含兩個結點,一個是存儲元素的數據域,一個是指向下一個結點地址的指針域。有單鏈表、雙向鏈表、循環鏈表。優勢:能夠任意加減元素,不須要初始化容量,添加刪除元素只需改變先後兩個元素結點的指針域便可。缺點:由於含有大量指針域,固佔用空間大,查找耗時。適用場景:數據量小,需頻繁增長刪除操做。sql
5)樹:由n個有限節點組成一種具備層次關係的集合。二叉樹(每一個結點最多有兩個子樹,結點的度最大爲2,左子樹和右子樹有順序)、紅黑樹(HashMap底層源碼)、B+樹(mysql的數據庫索引結構)數據庫
6)散列表(哈希表):根據鍵值對來存儲訪問。數組
7)堆:堆中某個節點的值老是不大於或不小於其父節點的值,堆老是一棵徹底二叉樹。安全
8)圖:由結點的有窮集合V和邊的集合E組成。數據結構
1)併發List,包括Vector和CopyOnWriteArrayList是兩個線程安全的List,Vector讀寫操做都用了同步,CopyOnWriteArrayList在寫的時候會複製一個副本,對副本寫,寫完用副本替換原值,讀時不須要同步。
2)併發Set,CopyOnWriteArraySet基於CopyOnWriteArrayList來實現的,不容許存在重複的對象。
3)併發Map,ConcurrentHashMap,內部實現了鎖分離,get操做是無鎖的。
4)併發Queue,ConcurrentLinkedQueue適用於高併發場景下的隊列,經過無鎖方式實現。 BlockingQueue阻塞隊列,應用場景,生產者-消費者模式,若生產快於消費,生產隊列裝滿時會阻塞,等待消費。
5)併發Deque, LinkedBlockingDueue沒有進行讀寫鎖分離,同一時間只能有一個線程對其操做。
6)併發鎖重入鎖ReentrantLock,互斥鎖,一次最多隻能一個線程拿到鎖。
7)讀寫鎖ReadWriteLock,有讀取和寫入鎖兩種,讀取容許多個讀取線程同時持有,而寫入只能有一個線程持有。
1)Collection接口:集合框架的根接口,它是集合類框架中最具通常性的頂層接口。
2)Map接口:提供了鍵值對的映射關係的集合,關鍵字不能有重複值,每一個關鍵字至多可映射一個值。HashMap(經過散列機制,用於快速訪問),TreeMap(保持key處於排序狀態,訪問速度不如hashmap), LinkedHashMap(保持元素的插入順序)
3)Set接口:可包含重複的元素,LinkedHashSet TreeSet(用紅黑樹來存儲元素) HashSet
4)List接口:可經過索引對元素進行精準的插入和查找,實現類有ArrayList LinkedList
5)Queue接口:繼承自Collection接口,LinkedList實現了Queue接口,提供了支持隊列的行爲。
6)Iterator接口:爲了迭代集合
7)Comparable接口:用於比較
Set是一個無序的集合,不能包含重複的元素;
list是一個有序的集合能夠包含重複的元素,提供了按索引訪問的方式;
map包含了key-value對,map中key必須惟一,value能夠重複。
1)數據結構
jdk1.7及之前,HashMap由數組+鏈表組成,數組Entry是HashMap的主體,Entry是HashMap中的一個靜態內部類,每個Entry包含一個key-value鍵值對,鏈表是爲解決哈希衝突而存在。
從jdk1.8起,HashMap是由數組+鏈表/紅黑樹組成,當某個bucket位置的鏈表長度達到閥值8時,這個鏈表就轉變成紅黑樹。
2)HashMap是線程不安全的,存儲比較快,能接受null值,HashMap經過put(key, value)來儲存元素,經過get(key)來獲得value值,經過hash算法來計算hashcode值,用hashcode標識Entry在bucket中存儲的位置。
3)HashMap中爲何要使用加載因子,爲何要進行擴容
加載因子是指當HashMap中存儲的元素/最大空間值的閥值,若是超過這個值,就會進行擴容。加載因子是爲了讓空間獲得充分利用,若是加載因子太大,雖對空間利用更充分,但查找效率會下降;若是加載因子過小,表中的數據過於稀疏,不少空間還沒用就開始擴容,就會對空間形成浪費。
至於爲何要擴容,若是不擴容,HashMap中數組處的鏈表會愈來愈長,這樣查找效率就會大大下降。
當咱們使用put(key, value)存儲對象到HashMap中時,具體實現步驟以下:
get(key)方法獲取key的hash值,計算hash&(n-1)獲得在鏈表數組中的位置first=table[hash&(n-1)],先判斷first(即數組中的那個)的key是否與參數key相等,不等的話,判斷結點是不是TreeNode類型,是則調用getTreeNode(hash, key)從二叉樹中查找結點,不是TreeNode類型說明仍是鏈表型,就遍歷鏈表找到相同的key值返回對應的value值便可。
當兩個對象的hashcode相同,它們的bucket位置相同,hashMap會用鏈表或是紅黑樹來存儲對象。Entry類裏有一個next屬性,做用是指向下一個Entry。第一個鍵值對A進來,經過計算其key的hash獲得index,記作Entry[index]=A。一會又進來一個鍵值對B,經過計算其key的hash也是index,HashMap會將B.next=A, Entry[index]=B.若是又進來C,其key的hash也是index,會將C.next=B, Entry[index]=C.這樣bucket爲index的地方存放了ABC三個鍵值對,它們能過next屬性鏈在一塊兒。數組中存儲的是最後插入的元素,其餘元素都在後面的鏈表裏。
當調用get方法時,hashmap會使用鍵對象的hashcode找到bucket位置,找到bucket位置後,會調用key.equals()方法去找到鏈表中正確的節點,最終找到值對象。
HashMap默認負載由於是0.75,當一個map填滿了75%的bucket時,和其餘集合類同樣,將會建立原來HashMap大小兩倍的bucket數組,來從新調整HashMap的大小,並將原來的對象放入新的bucket數組中。
在jdk1.7及之前,多線程擴容可能出現死循環。由於在調整大小過程當中,存儲在某個bucket位置中的鏈表元素次序會反過來,而多線程狀況下可能某個線程翻轉完鏈表,另一個線程又開始翻轉,條件競爭發生了,那麼就死循環了。
而在jdk1.8中,會將原來鏈表結構保存至節點e中,將原來數組中的位置設爲null,而後依次遍歷e,根據hash&n是否爲0分紅兩條支鏈,保存在新數組中。若是多線程狀況可能會取到null值形成數據丟失。
1)jdk1.7及之前:一個ConcurrentHashMap由一個segment數組和多個HashEntry組成,每個segment都包含一個HashEntry數組, Segment繼承ReentrantLock用來充當鎖角色,每個segment包含了對本身的HashEntry的操做,如getputreplace操做,這些操做發生時,對本身的HashEntry進行鎖定。因爲每個segment寫操做只鎖定本身的HashEntry,能夠存在多個線程同時寫的狀況。
jdk1.8之後:ConcurrentHashMap取消了segments字段,採用transient volatile HashEntry<K, V> table保存數據,採用table數組元素做爲鎖,實現對每個數組數據進行加鎖,進一小減小併發衝突機率。ConcurrentHashMap是用Node數組+鏈表+紅黑樹數據結構來實現的,併發制定用synchronized和CAS操做。
2)Segment實現了ReentrantLock重入鎖,當執行put操做,會進行第一次key的hash來定位Segment的位置,若該Segment尚未初始化,會經過CAS操做進行賦值,再進行第二次hash操做,找到相應的HashEntry位置。
1)存儲方式不同,HashMap內部有一個Node<K,V>[]對象,每一個鍵值對都會存儲到這個對象裏,當用put方法添加鍵值對時,會new一個Node對象,tab[i] = newNode(hash, key, value, next);
ArrayMap存儲則是由兩個數組來維護,int[] mHashes; Object[] mArray; mHashes數組中保存的是每一項的HashCode值,mArray存的是鍵值對,每兩個元素表明一個鍵值對,前面保存key,後面保存value。mHashes[index]=hash; mArray[index<<1]=key; mArray[(index<<1)+1]=value;
ArrayMap相對於HashMap,無需爲每一個鍵值對建立Node對象,且在數組中連續存放,更省空間。
2)添加數據時擴容處理不同,進行了new操做,從新建立對象,開銷很大;而ArrayMap用的是copy數據,全部效率相對高些;
3)ArrayMap提供了數組收縮功能,在clear或remove後,會從新收縮數組,釋放空間;
4)ArrayMap採用二分法查找,mHashes中的hash值是按照從小到大的順序連續存放的,經過二分查找來獲取對應hash下標index,去mArray中查找鍵值對。mHashes中的index2是mArray中的key下標,index2+1爲value的下標,因爲存在hash碰撞狀況,二分查找到的下標多是多個連續相同的hash值中的任意一個,此時須要用equals比對命中的key對象是否相等,不相等,應當從當前index先向後再向前遍歷全部相同hash值。
5)sparseArray比ArrayMap進一步優化空間,SparseArray專門對基本類型作了優化,Key只能是可排序的基本類型,如intlong,對value,除了泛型Value,還對每種基本類型有單獨實現,如SparseBooleanArraySparseLongArray等。無需包裝,直接使用基本類型值,無需hash,直接使用基本類型值索引和判斷相等,無碰撞,無需調用hashCode方法,無需equals比較。SparseArray延遲刪除。
Hashtable中的無參構造方法Hashtable()中調用了this(11, 0.75f),說明它默認容量是11,加載因子是0.75,在構造方法上會new HashtableEntry<?, ?>[initialCapacity]; 會新建一個容量是初始容量的HashtableEntry數組。
HashtableEntry數組中包含hashKeyValuenext變量,鏈表形式,重寫了hashCode和equals方法。Hashtable全部public方法都在方法體上加上了synchronized鎖操做,說明它是線程安全的。
它還實現了Serializable接口中的writeObject和readObject方法,分別實現了逐行讀取和寫入的功能,而且加了synchronized鎖操做。
根所key.hashCode(),計算它在table表中的位置,(hash&0x7FFFFFFF)%tab.length,遍歷該索引處表的位置中是否有值,是否存在鏈表,再判斷是key值和hash值是否相等,相等則返回對應的value值。
1)Hashtable是個線程安全的類,在對外方法都添加了synchronized方法,序列化方法上也添加了synchronized同步鎖方法,而HashMap非線程安全。這也致使Hashtable的讀寫等操做比HashMap慢。
2)Hashtable不容許值和鍵爲空,若爲空會拋出空指針。而HashMap容許鍵和值爲空;
3)Hashtable根據key值的hashCode計算索引,(hash&0x7FFFFFFF)%tab.length,保證hash值始終爲正數且不超過表的長度。而HashMap中計算索引值是經過hash(key)&(tab.length-1),是經過與操做,計算出在表中的位置會比Hashtable快。
4)Hashtable容量能爲任意大於等於1的正數,而HashMap的容量必須爲2^n,Hashtable默認容量爲11,HashMap初始容量爲16
5)Hashtable每次擴容,新容量爲舊容量的2倍+1,而HashMap爲舊容量的2倍。
HashSet底層實現是HashMap,內部包含一個HashMap<E, Ojbect> map變量
private transient HashMap<E,Object> map;
一個Object PRESENT變量(當成插入map中的value值)
private static final Object PRESENT = new Object();
HashSet中元素都存到HashMap鍵值對的Key上面。具體能夠查看HashSet的add方法,直接調用了HashMap的put方法,將值做爲HashMap的鍵,值用一個固定的PRESENT值。
public boolean add(E e) { return map.put(e, PRESENT)==null; }
HashSet沒有單獨的get方法,用的是HashMap的。HashSet實現了Set接口,不容許集合中出現重複元素,將對象存儲進HashSet前,要先確保對象重寫了hashCode()和equals方法,以保證放入set對象是惟一的。
HashMap在放入key-value鍵值對是,先經過key計算其hashCode()值,再與tab.length-1作與操做,肯定下標index處是否有值,若是有值,再調用key對象的equals方法,對象不一樣則插入到表頭,相同則覆蓋;
HashSet是將數據存放到HashMap的key中,HashMap是key-value形式的數據結構,它的key是惟一的,HashSet利用此原理保證放入的對象惟一性。
HashSet底層實現是HashMap,HashMap若是兩個不一樣Key對象的hashCode()值相等,會用鏈表存儲,HashSet也同樣。
ArrayList底層是用數組實現的,隨着元素添加,其大小是動態增大的;在內存中是連續存放的;若是在集合末尾添加或刪除元素,所用時間是一致的,若是在列表中間添加或刪除元素,所用時間會大大增長。經過索引查找元素速度很快。適合場合:查詢比較多的場景
LinkedList底層是經過雙向鏈表實現的,LinkedList和ArrayList相比,增刪速度快,但查詢和修改值速度慢。在內存中不是連續內存。場景:增刪操做比較多的場景。
(更多完整項目下載。未完待續。源碼。圖文知識後續上傳github。)
領取完整版PDF