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