HashMap VS HashTable

HashMap VS HashTable


	相同點:
		1>都是Map的子類。
		2>都是基於Entry數組實現的。
		
	不同點:
		1>HashMap多線程下是不安全的,HashTable是線程安全的。
		2>HashMap的key和value都允許爲null,HashTable的key和value都不允許爲null(key或value爲null時會拋出空指針異常)。
		3>HashMap的默認容量是16,擴容後的容量是之前的2倍;HashTable的默認容量是11,擴容後的容量是之前的2倍+1。
		4>獲取bucket的方式不同:
			-------------------------------------------------
			HashTable獲取數組下標的方式:取模法
			代碼:
				int hash = hash(key);
				int index = (hash & 0x7FFFFFFF) % tab.length;
			說明:
				1)根據key獲得一個hashValue[注:hashValue=hash(key)&0x7FFFFFFF],然後用hashValue對數組的長度取模得到數組的下標,即:hashValue%length
				2)取模法基本能保證元素在哈希表中散列的比較均勻,但是取模會用到除法運算,效率很低。

			-------
			HashMap獲取數組下標的方式:按位與
			代碼:
				int hash = hash(key);
				int i = indexFor(hash, table.length);
				static int indexFor(int h, int length) {
					return h & (length-1);
				}
			說明:
				1)根據key獲得一個hashValue[注:hashValue=hash(key)],然後用hashValue對length-1進行按位與運算得到數組的下標,即:hashValue&(length-1)
				2)數組的length必須是2的整數次冪,原因如下:
					第一:若length是2的整數次冪,則hashValue&(length-1)等價於hashValue%length,那麼hashValue&(length-1)同樣也實現了均勻散列,但是(位運算)效率會更高。
						1>歸納:
							2^1 -1 = 0000 0001 
							2^2 -1 = 0000 0011 
							2^3 -1 = 0000 0111 
							2^n -1 = 0000 (n個1) 
						2>舉例:
							若:hashValue=29,length=16
							則:hashValue & (length -1)  ==>  29 & (2^4-1)  ==>  00011101 & 00001111 = 00001101 ==>  13  ==>  0<= hashValue & (length -1) <=length -1
								hashValue % length		 ==>  29 % 16		==>	 13										 ==>  0<= hashValue % length <=length -1
							故:hashValue & (length -1) == hashValue % length
						3>結論:當length=2^n時,hashValue & (length -1) == hashValue % length,且二者的結果範圍都是:0到length-1之間的整數。
				  
					第二:若length爲奇數,則length-1爲偶數,偶數(二進制)的最後一位是0,從而導致hashValue&(length-1)的最後一位永遠爲0,
						  即:hashValue&(length-1)的結果永遠爲偶數,最終導致數組中下標爲奇數的空間全部被浪費掉。
			-------------------------------------------------
				
				
HashMap在jdk8中的優化:
	數據結構:
		jdk7:數組+鏈表
		jdk8:數組+鏈表+紅黑樹
	
	鏈表插入新節點的方式:
		jdk7:新節點添加到頭部
		jdk8:新節點添加到尾部
		
	擴容:
		jdk7:初始容量16,容量到達閾值後進行擴容,每次擴容後,容量變爲之前的2倍。
		jdk8:
			初始容量16,容量到達閾值後進行擴容,每次擴容後,容量變爲之前的2倍。
			當鏈表長度大於等於8且當前容量還沒達到最小樹化容量(64)時,會進行擴容以減少衝突。這個邏輯在樹化方法中:java.util.HashMap#treeifyBin
		說明:
			剛開始時HashMap的容量較小,故哈希碰撞的機率會比較大一些,即出現長鏈表的可能性會稍微大一些。
			因爲容量較小而產生的長鏈表,我們應該優先選擇擴容來降低衝突,而不是樹化。
			(eg:可能存在多個長鏈表,一次擴容可以同時降低這些鏈表的長度,若選擇樹化來降低衝突,則需要操作n次)
				
	jdk8的樹化(將鏈表轉化爲紅黑樹)和去樹化:
		當容量超過最小樹化容量(64)時,如果存在鏈表長度大於等於樹化閾值(8)時就會樹化,故最壞的情況下的查找時間複雜度爲O(logN)。jdk7最壞的情況下會遍歷整個鏈表,時間複雜度爲O(N)。
		擴容的時候,若發現紅黑樹中節點的數量小於等於去樹化閾值(6)時,會將紅黑樹轉換爲鏈表。
			說明:
				若將去樹化的閾值也設計爲7,則當一個HashMap不停的 插入元素,再刪除元素 時就會導致不斷地進行樹化和去樹化的操作,導致效率降低。
				TreeNodes佔用的空間是普通Node的兩倍,這樣設計是爲了追求時間和空間的平衡。
		
	線程安全:
		jdk7:
			線程不安全,多個線程同時擴容時,可能會生成循環鏈表,導致cpu飆高。
		jdk8:
			線程不安全,不會生成循環鏈表,但是put操作時存在丟失數據的情況。
			


TreeMap
	數據結構:基於紅黑樹實現。
	特點:鍵值對默認根據key升序排序,也可以自己定義比較器。
	
	
	
	

 

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