HashMap的存儲結構及原理


1、HashMap的數據結構(HashMap通過hashcode對其內容進行高速查找,是無序的)


  數據結構中有數組和鏈表來實現對數據的存儲,但這兩者基本上是兩個極端。

  數組 :數組的存儲區是連續的,占用內存嚴重,故空間復雜度非常大。但數組的二分查找時間度小;數組的特點:尋址easy,插入和

刪除困難。

  鏈表 :鏈表的儲存區離散。占用內存比較寬松。故空間復雜度非常小,但時間復雜度大;鏈表的特點:尋址困難,插入和刪除easy。


哈希表

  HashMap是由數組+鏈表組成。尋址easy,插入和刪除easy。(存儲單元數組Entry[],數組里面包括鏈表

  HashMap事實上也是由一個線性的數組實現的。

所以能夠理解為其存儲數據的容器就是一個線性容器;

  HashMap里面有一個內部靜態類Entry,其重要的屬性有key,value,next,從屬性key,value 就能夠非常明顯的看出來 Entry就是

  HashMap鍵值對實現的一個基礎bean;也就是說HashMap的基礎就是一個線性數組,這個數組就是Entry[]。Map里面的內容都保存

在Entry[]中;

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */

    transient Entry[] table;

2、HashMap的存取實現


2.1:存儲

這里HashMap用了一個算法。

//存儲時候:

int hash=key.hashCode(); //獲取key的hashCode,這個值是一個固定的int值

int index=hash%Entry[].length。//獲取數組下標:key的hash值對Entry數組長度進行取余

Entry[index]=value。


注意:假設兩個key通過hash%Entry[].length得到的index同樣。會不會覆蓋?

是不會的。Entry類有一個next屬性,作用是指向下一個Entry。打個例如, 第一個鍵值對A進來。通過計算其key的hash得到的

index=0。記做:Entry[0] = A。一會后又進來一個鍵值對B,通過計算其index也等於0,如今怎么辦?HashMap會這樣做:B.next =

 A,Entry[0] = B,假設又進來C,index也等於0,那么C.next = B,Entry[0] = C;這樣我們發現index=0的地方事實上存取了A,B,C三個鍵值對,他

們通過next這個屬性鏈接在一起。

所以疑問不用操心。

也就是說Entry[]數組中存儲的是最后插入的數據


	 public V put(K key, V value) {
	        if (key == null)
	            return putForNullKey(value); //null總是放在數組的第一個鏈表中
	        int hash = hash(key.hashCode());
	        int i = indexFor(hash, table.length);
	        //遍歷鏈表
	        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
	            Object k;
	            //假設key在鏈表中已存在,則替換為新value
	            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
	                V oldValue = e.value;
	                e.value = value;
	                e.recordAccess(this);
	                return oldValue;
	            }
	        }
	        modCount++;
	        addEntry(hash, key, value, i);
	        return null;
	    }

	 
	void addEntry(int hash, K key, V value, int bucketIndex) {
	    Entry<K,V> e = table[bucketIndex];
	    table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //參數e, 是Entry.next
	    //假設size超過threshold,則擴充table大小。再散列
	    if (size++ >= threshold)
	            resize(2 * table.length);
	}


2.2:取值

    獲取key的hashcode指,通過hash值去hash%Entry[].length  獲取Entry[hash%Entry[].length],定位到該數組元素之后,再遍歷該元

素處的鏈表。

//取值時候:

int hash=key.hashCode();

int index =hash%Entry[].length;

return Entry[index];


     public V get(Object key) {
	        if (key == null)
	            return getForNullKey();
	        int hash = hash(key.hashCode());
	        //先定位到數組元素。再遍歷該元素處的鏈表
	        for (Entry<K,V> e = table[indexFor(hash, table.length)];
	             e != null;
	             e = e.next) {
	            Object k;
	            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
	                return e.value;
	        }
	        return null;
	}


      當哈希表的容量超過默認容量時,必需要調整table的大小。

當容量達到最大值時,該方法Integer.MAX_VALUE返回。這時。就需要創建

一張表,將原來的表映射到新表中。


3、HashMap、HashTable和ConcurrentHashMap的線程安全問題

HashMap:線程不安全的。


HashTable:鎖住整張hash表,讓線程獨占。hashMap同意為空。

通過分析Hashtable就知道,synchronized是針對整張Hash表的,每次鎖住整張表

讓線程獨占。安全的背后是巨大的浪費。

ConcurrentHashMap:一個更快的hashmap,它提供了好得多的並發性。多個讀操作差點兒總能夠並發地運行。

他是鎖段(默認:把hash表分為16個

,在get,put,remove等操作中,ConcurrentHashMap僅僅鎖定當前須要用到的段,僅僅有在求size的時候才鎖定整張hash表。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM