hashmap是雙鏈表格式的存儲結構<K,V>存儲數據,沒有順序性,1.7基於hash表存儲。容許空值存在,鍵中有且只容許有一個,值中也容許有空值存在。初始容量大小爲16加載因子爲0.75。 線程不安全,在並操做時存在安全問題。
初始化的時候無參狀況下,使用默認初始大小和加載因子進行初始化。html
//空參構造方法 public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } //構造方法 //執行構造方法以前會加載類中的變量 //初始化一個Entry對象數組 //transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; threshold = initialCapacity; init(); }
public V put(K key, V value) { //是否爲空Entry<K,V> table if (table == EMPTY_TABLE) { inflateTable(threshold); } //key爲null,hashMap專門空值設置一個存儲方法 if (key == null) return putForNullKey(value); //計算hash int hash = hash(key); //計算index,根絕key的hash和table的長度 int i = indexFor(hash, table.length); //是否有重複的key值,經過獲取現有的table[i]是否爲空來判斷 //當不一樣key的hash值相同時爲hash衝突,hash衝突時,須要便利全部的Entry比較是否key的==和equal方法一致。 //hash一致後Entry變爲爲鏈狀,同一個index下有多個Entry[]數據,並把添加放置到重複index下的Entry中的最後一個 for (Entry<K,V> e = table[i]; e != null; e = e.next) { //重複key值處理 Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; //添加Entry addEntry(hash, key, value, i); return null; }
addEntry方法java
void addEntry(int hash, K key, V value, int bucketIndex) { //當前entry的size大小大於threshold(size*0.75加載因子)而且當前表的index值已經存在 //散列表散列值計算,一般是兩種方法:鏈表法和開放地址法 //鏈表法就是將相同hash值的對象組織成一個鏈表放在hash值對應的槽位;開放地址法是經過一個探測算法,當某個槽位已經被佔據的狀況下繼續查找下一個可使用的槽位。 //1.7hashMap使用的就是鏈表法 if ((size >= threshold) && (null != table[bucketIndex])) { //當大小超過threshold而且出現hash衝突的時候會擴容在不大於最大值的狀況下是是舊錶的二倍 resize(2 * table.length); //是否從新計算hash hash = (null != key) ? hash(key) : 0; //給衝突的hash集合新表的長度再次計算hash bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); }
resize方法算法
void resize(int newCapacity) { //舊錶 Entry[] oldTable = table; int oldCapacity = oldTable.length; //若是已是最大長度則直接返回 if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; //遷移表 transfer(newTable, initHashSeedAsNeeded(newCapacity)); table = newTable; //從新計算 閥值 threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); }
threshold方法數組
void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; //遍歷舊錶對遷移新表 for (Entry<K,V> e : table) { //在對Entry鏈表的複製過程當中可能會存在環的問題。多條線程併發操做,遍歷Enrt表的時候會致使環的存在,新鏈表插入相比較舊鏈表而言是倒敘,由於多線程快慢問題可能致使。 //對有bucket鏈進行便利 while(null != e) { //記錄舊錶的下一個entry值 Entry<K,V> next = e.next; //是否從新計算hash if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } //計算idex值 int i = indexFor(e.hash, newCapacity); //新表的指針反轉舊錶指向 e.next = newTable[i]; //舊的enry攜帶新的指向賦值給新的槽位 newTable[i] = e; //舊錶指針往下 e = next; } } }
public V get(Object key) { if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); }
getEntry方法安全
final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); //hash重複的狀況 //比較key的equals和==的方法 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 != null && key.equals(k)))) return e; } return null; }
public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); }
removeEnryForKey
方法多線程
final Entry<K,V> removeEntryForKey(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); int i = indexFor(hash, table.length); //刪除 Entry<K,V> prev = table[i]; Entry<K,V> e = prev; while (e != null) { Entry<K,V> next = e.next; Object k; //hash相同時 if (e.hash == hash &&(k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }