HashMap和ConcurrentHashMap的區別,HashMap的底層源碼


HashMap和ConcurrentHashMap的區別:

  1. HashMap不是線程安全的,而ConcurrentHashMap是線程安全的。
  2. ConcurrentHashMap采用鎖分段技術,將整個Hash桶進行了分段segment,也就是將這個大的數組分成了幾個小的片段segment,而且每個小的片段segment上面都有鎖存在,那么在插入元素的時候就需要先找到應該插入到哪一個片段segment,然后再在這個片段上面進行插入,而且這里還需要獲取segment鎖。

  3. ConcurrentHashMap讓鎖的粒度更精細一些,並發性能更好。

 

HashMap的底層源碼:

一. 數據結構

      Map將實際數據存儲在Entry類的數組中。

transient Entry[] table;    //HashMap的成員變量,存放數據  
  
static class Entry<K,V> implements Map.Entry<K,V> {    //內部類Entry  
    final K key;  
    V value;  
    Entry<K,V> next;     //指向下一個數據  
    final int hash;  
  
    /** 
     * Creates new entry. 
     */  
    Entry(int h, K k, V v, Entry<K,V> n) {  
        value = v;  
        next = n;  
        key = k;  
        hash = h;  
    }  

       執行put方法時,根據key的hash值來計算放到table數組的下標,如果hash有相同的下標,則新put進去的元素放到Entry鏈的頭部。

 

二. 屬性和構造方法

屬性:

1 static final int DEFAULT_INITIAL_CAPACITY = 16;  //默認的初始大小,如果執行無參的構造方法,則默認初始大小為16  
2 static final int MAXIMUM_CAPACITY = 1 << 30;  //最大容量,1073741824  
3 static final float DEFAULT_LOAD_FACTOR = 0.75f;  //默認的負載因子,如果沒有通過構造方法傳入負載因子,則使用0.75。  
4 transient Entry[] table;   //存放具體鍵值對的Entry數組  
5 transient int size;   //HashMap的大小  
6 int threshold;    //閥值  threshold = (int)(capacity * loadFactor); 即容量*負載因子,執行put方法時如果size大於threshold則進行擴容,后面put方法將會看到  
7 final float loadFactor;   //用戶設置的負載因子  
8 transient volatile int modCount;    //HashMap實例被改變的次數,這個同ArrayList  

 

構造方法一:

1 public HashMap() {  
2     this.loadFactor = DEFAULT_LOAD_FACTOR;  
3     threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);  
4     table = new Entry[DEFAULT_INITIAL_CAPACITY];  
5     init();  
6 } 

使用了默認的容量和默認的負載因子。

 

構造方法二:

1 public HashMap(int initialCapacity) {  
2     this(initialCapacity, DEFAULT_LOAD_FACTOR);  
3 } 

使用了用戶設置的初始容量和默認的負載因子。

 

構造方法三:

 1 public HashMap(int initialCapacity, float loadFactor) {  
 2     if (initialCapacity < 0)  
 3         throw new IllegalArgumentException("Illegal initial capacity: " +  
 4                                            initialCapacity);  
 5     if (initialCapacity > MAXIMUM_CAPACITY)  
 6         initialCapacity = MAXIMUM_CAPACITY;  
 7     if (loadFactor <= 0 || Float.isNaN(loadFactor))  
 8         throw new IllegalArgumentException("Illegal load factor: " +  
 9                                            loadFactor);  
10   
11     // Find a power of 2 >= initialCapacity  
12     int capacity = 1;  
13     while (capacity < initialCapacity)  
14         capacity <<= 1;  
15   
16     this.loadFactor = loadFactor;  
17     threshold = (int)(capacity * loadFactor);  
18     table = new Entry[capacity];  
19     init();  
20 }  

用戶傳入了初始容量和負載因子,這兩個值是HashMap性能優化的關鍵,涉及到了HashMap的擴容問題。

HashMap的容量永遠是2的倍數,如果傳入的不是2的指數則被調整為大於傳入值的最近的2的指數,例如如果傳入130,則capacity計算后是256。是這段代碼起的作用:

1 while (capacity < initialCapacity)  
2         capacity <<= 1; 

 

構造方法四:

 1 public HashMap(Map<? extends K, ? extends V> m) {  
 2     this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,  
 3                   DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);//計算Map的大小  
 4     putAllForCreate(m);//初始化  
 5 }  
 6   
 7 private void putAllForCreate(Map<? extends K, ? extends V> m) {  
 8     for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {//通過entryset進行遍歷  
 9         Map.Entry<? extends K, ? extends V> e = i.next();  
10         putForCreate(e.getKey(), e.getValue());  
11     }  
12 }  

根據傳入的map進行初始化。

 

三. 關鍵方法

1) put方法

 1 public V put(K key, V value) {  
 2     if (key == null)  
 3         return putForNullKey(value);  //單獨處理,總是放到table[0]中  
 4     int hash = hash(key.hashCode());  //計算key的hash值,后面介紹性能的時候再說這個hash方法。  
 5     int i = indexFor(hash, table.length);  //將hash和length-1取&來得到數組的下表  
 6     for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
 7         Object k;  
 8         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
 9             V oldValue = e.value;  
10             e.value = value;  
11             e.recordAccess(this);  
12             return oldValue;  
13         }  
14     }  //如果這個key值,原來已經則替換后直接返回。  
15     modCount++;  
16     addEntry(hash, key, value, i);  
17     return null;  
18 }  
19 void addEntry(int hash, K key, V value, int bucketIndex) {  
20 Entry<K,V> e = table[bucketIndex];  
21        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  //如果table[bucketIndex]中已經存在Entry則放到頭部。  
22        if (size++ >= threshold)  //如果大於了閥值,則擴容到原來大小的2倍。  
23            resize(2 * table.length);  
24    }  
25   
26 void resize(int newCapacity) {  
27     Entry[] oldTable = table;  
28     int oldCapacity = oldTable.length;  
29     if (oldCapacity == MAXIMUM_CAPACITY) {  
30         threshold = Integer.MAX_VALUE;  
31         return;  
32     }  
33   
34     Entry[] newTable = new Entry[newCapacity];  
35     transfer(newTable);  //賦值到新的table中,注意轉移后會重新hash,所以位置可能會跟之前不同,目的是均勻分不到新的table中。  
36     table = newTable;  
37     threshold = (int)(newCapacity * loadFactor);  
38 } 

2) get方法

 1 public V get(Object key) {  
 2     if (key == null)  
 3         return getForNullKey();  
 4     int hash = hash(key.hashCode());  
 5     for (Entry<K,V> e = table[indexFor(hash, table.length)];//找到數組的下表,進行遍歷  
 6          e != null;  
 7          e = e.next) {  
 8         Object k;  
 9         if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
10             return e.value;//找到則返回  
11     }  
12     return null;//否則,返回null  
13 }  

3) remove方法

 1 public V remove(Object key) {  
 2     Entry<K,V> e = removeEntryForKey(key);  
 3     return (e == null ? null : e.value);  
 4 }  
 5   
 6 final Entry<K,V> removeEntryForKey(Object key) {  
 7     int hash = (key == null) ? 0 : hash(key.hashCode());  
 8     int i = indexFor(hash, table.length);  
 9     Entry<K,V> prev = table[i];  
10     Entry<K,V> e = prev;  
11   
12     while (e != null) {//Entry鏈未遍歷完則一直遍歷  
13         Entry<K,V> next = e.next;  
14         Object k;  
15         if (e.hash == hash &&  
16             ((k = e.key) == key || (key != null && key.equals(k)))) {  
17             modCount++;  
18             size--;  
19             if (prev == e)//如果是第一個,則將table[i]執行e.next  
20                 table[i] = next;  
21             else //否則將前一個的next指向e.next  
22                 prev.next = next;   
23             e.recordRemoval(this);  
24             return e;  
25         }  
26         prev = e;//未找到則繼續往后遍歷  
27         e = next;  
28     }  
29   
30     return e;  
31 }  

4) HashMap的遍歷方法

 1 Map map = new HashMap();  
 2 map.put("key1","value1");  
 3 map.put("key2","value2");  
 4 map.put("key3", "value3");  
 5 for(Iterator it = map.entrySet().iterator();it.hasNext();){  
 6     Map.Entry e = (Map.Entry)it.next();  
 7     System.out.println(e.getKey() + "=" + e.getValue());  
 8 }  
 9 System.out.println("-----------------------------------------");  
10 for(Iterator it = map.keySet().iterator();it.hasNext();){  
11     Object key = it.next();  
12     Object value = map.get(key);  
13     System.out.println(key+"="+value);  
14 }  
15 System.out.println("-----------------------------------------");  
16 System.out.println(map.values());  
17   
18 輸出為:  
19 key3=value3  
20 key2=value2  
21 key1=value1  
22 -----------------------------------------  
23 key3=value3  
24 key2=value2  
25 key1=value1  
26 -----------------------------------------  
27 [value3, value2, value1]  

 

                                                      原文地址: http://frank1234.iteye.com/blog/2162490

 


免責聲明!

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



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