1 public interface Map<K,V> { 2 int size(); 3 boolean isEmpty(); 4 boolean containsKey(Object key); 5 boolean containsValue(Object value); 6 V get(Object key); 7 V put(K key, V value); 8 V remove(Object key); 9 void putAll(Map<? extends K, ? extends V> m); 10 void clear(); 11 Set<K> keySet(); 12 Collection<V> values(); 13 Set<Map.Entry<K, V>> entrySet(); 14 interface Entry<K,V> { 15 V getValue(); 16 V setValue(V value); 17 boolean equals(Object o); 18 int hashCode(); 19 } 20 boolean equals(Object o); 21 int hashCode(); 22 }
1 public class MyMap { 2 3 private Entry[] data = new Entry[100]; 4 private int size; 5 6 public Object put(Object key, Object value) { 7 // 檢查key是否存在,存在則覆蓋 8 for (int i = 0; i < size; i++) { 9 if (key.equals(data [i].key)) { 10 Object oldValue = data[i].value ; 11 data[i].value = value; 12 return oldValue; 13 } 14 } 15 16 Entry e = new Entry(key, value); 17 data[size ] = e; 18 size++; 19 20 return null; 21 } 22 23 public Object get(Object key) { 24 for (int i = 0; i < size; i++) { 25 if (key.equals(data [i].key)) { 26 return data [i].value; 27 } 28 } 29 30 return null; 31 } 32 33 public int size() { 34 return size ; 35 } 36 37 private class Entry { 38 Object key; 39 Object value; 40 41 public Entry(Object key, Object value) { 42 this.key = key; 43 this.value = value; 44 } 45 46 } 47 }
1 public class Test { 2 3 public static void main(String[] args) { 4 MyMap map = new MyMap(); 5 map.put( "tstd", "angelababy" ); 6 map.put( "張三" , "李四"); 7 map.put( "tstd", "高圓圓" ); 8 9 System. out.println(map.size()); 10 System. out.println(map.get("tstd" )); 11 System. out.println(map.get("張三" )); 12 } 13 }
看下結果:html
2
高圓圓
李四
1 public class HashMap<K,V> 2 extends AbstractMap<K,V> 3 implements Map<K,V>, Cloneable, Serializable
1 // 默認初始容量爲16,必須爲2的n次冪 2 static final int DEFAULT_INITIAL_CAPACITY = 16; 3 4 // 最大容量爲2的30次方 5 static final int MAXIMUM_CAPACITY = 1 << 30; 6 7 // 默認加載因子爲0.75f 8 static final float DEFAULT_LOAD_FACTOR = 0.75f; 9 10 // Entry數組,長度必須爲2的n次冪 11 transient Entry[] table; 12 13 // 已存儲元素的數量 14 transient int size ; 15 16 // 下次擴容的臨界值,size>=threshold就會擴容,threshold等於capacity*load factor 17 int threshold; 18 19 // 加載因子 20 final float loadFactor ;
能夠看出HashMap底層是用Entry數組存儲數據,同時定義了初始容量,最大容量,加載因子等參數,至於爲何容量必須是2的冪,加載因子又是什麼,下面再說,先來看一下Entry的定義。前端
1 static class Entry<K,V> implements Map.Entry<K,V> { 2 final K key ; 3 V value; 4 Entry<K,V> next; // 指向下一個節點 5 final int hash; 6 7 Entry( int h, K k, V v, Entry<K,V> n) { 8 value = v; 9 next = n; 10 key = k; 11 hash = h; 12 } 13 14 public final K getKey() { 15 return key ; 16 } 17 18 public final V getValue() { 19 return value ; 20 } 21 22 public final V setValue(V newValue) { 23 V oldValue = value; 24 value = newValue; 25 return oldValue; 26 } 27 28 public final boolean equals(Object o) { 29 if (!(o instanceof Map.Entry)) 30 return false; 31 Map.Entry e = (Map.Entry)o; 32 Object k1 = getKey(); 33 Object k2 = e.getKey(); 34 if (k1 == k2 || (k1 != null && k1.equals(k2))) { 35 Object v1 = getValue(); 36 Object v2 = e.getValue(); 37 if (v1 == v2 || (v1 != null && v1.equals(v2))) 38 return true; 39 } 40 return false; 41 } 42 43 public final int hashCode() { 44 return (key ==null ? 0 : key.hashCode()) ^ 45 ( value==null ? 0 : value.hashCode()); 46 } 47 48 public final String toString() { 49 return getKey() + "=" + getValue(); 50 } 51 52 // 當向HashMap中添加元素的時候調用這個方法,這裏沒有實現是供子類回調用 53 void recordAccess(HashMap<K,V> m) { 54 } 55 56 // 當從HashMap中刪除元素的時候調動這個方法 ,這裏沒有實現是供子類回調用 57 void recordRemoval(HashMap<K,V> m) { 58 } 59 }
Entry是HashMap的內部類,它繼承了Map中的Entry接口,它定義了鍵(key),值(value),和下一個節點的引用(next),以及hash值。很明確的能夠看出Entry是什麼結構,它是單線鏈表的一個節點。也就是說HashMap的底層結構是一個數組,而數組的元素是一個單向鏈表。算法
1 /** 2 * 構造一個指定初始容量和加載因子的HashMap 3 */ 4 public HashMap( int initialCapacity, float loadFactor) { 5 // 初始容量和加載因子合法校驗 6 if (initialCapacity < 0) 7 throw new IllegalArgumentException( "Illegal initial capacity: " + 8 initialCapacity); 9 if (initialCapacity > MAXIMUM_CAPACITY) 10 initialCapacity = MAXIMUM_CAPACITY; 11 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 12 throw new IllegalArgumentException( "Illegal load factor: " + 13 loadFactor); 14 15 // Find a power of 2 >= initialCapacity 16 // 確保容量爲2的n次冪,是capacity爲大於initialCapacity的最小的2的n次冪 17 int capacity = 1; 18 while (capacity < initialCapacity) 19 capacity <<= 1; 20 21 // 賦值加載因子 22 this.loadFactor = loadFactor; 23 // 賦值擴容臨界值 24 threshold = (int)(capacity * loadFactor); 25 // 初始化hash表 26 table = new Entry[capacity]; 27 init(); 28 } 29 30 /** 31 * 構造一個指定初始容量的HashMap 32 */ 33 public HashMap( int initialCapacity) { 34 this(initialCapacity, DEFAULT_LOAD_FACTOR); 35 } 36 37 /** 38 * 構造一個使用默認初始容量(16)和默認加載因子(0.75)的HashMap 39 */ 40 public HashMap() { 41 this.loadFactor = DEFAULT_LOAD_FACTOR; 42 threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); 43 table = new Entry[DEFAULT_INITIAL_CAPACITY]; 44 init(); 45 } 46 47 /** 48 * 構造一個指定map的HashMap,所建立HashMap使用默認加載因子(0.75)和足以容納指定map的初始容量。 49 */ 50 public HashMap(Map<? extends K, ? extends V> m) { 51 // 確保最小初始容量爲16,並保證能夠容納指定map 52 this(Math.max(( int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, 53 DEFAULT_INITIAL_CAPACITY ), DEFAULT_LOAD_FACTOR); 54 putAllForCreate(m); 55 }
最後一個構造方法引入一下三個方法進行map元素添加,具體內容很少看了,邏輯和put同樣可是少了數組擴容邏輯,直接跳過去看增長方法。數組
1 private void putAllForCreate(Map<? extends K, ? extends V> m) { 2 for(Iterator<?extendsMap.Entry<?extendsK, ?extendsV>> i = m.entrySet().iterator(); i.hasNext(); ) { 3 Map.Entry<? extends K, ? extends V> e = i.next(); 4 putForCreate(e.getKey(), e.getValue()); 5 } 6 } 7 8 /** 9 * This method is used instead of put by constructors and 10 * pseudoconstructors (clone, readObject). It does not resize the table, 11 * check for comodification, etc. It calls createEntry rather than 12 * addEntry. 13 */ 14 private void putForCreate(K key, V value) { 15 int hash = (key == null) ? 0 : hash(key.hashCode()); 16 int i = indexFor(hash, table.length ); 17 18 for (Entry<K,V> e = table [i]; e != null; e = e. next) { 19 Object k; 20 if (e.hash == hash && 21 ((k = e. key) == key || (key != null && key.equals(k)))) { 22 e. value = value; 23 return; 24 } 25 } 26 27 createEntry(hash, key, value, i); 28 } 29 30 void createEntry(int hash, K key, V value, int bucketIndex) { 31 Entry<K,V> e = table[bucketIndex]; 32 table[bucketIndex] = new Entry<K,V>(hash, key, value, e); 33 size++; 34 }
1 public V put(K key, V value) { 2 // 若是key爲null,調用putForNullKey方法進行存儲 3 if (key == null) 4 return putForNullKey(value); 5 // 使用key的hashCode計算key對應的hash值 6 int hash = hash(key.hashCode()); 7 // 經過key的hash值查找在數組中的index位置 8 int i = indexFor(hash, table.length ); 9 // 取出數組index位置的鏈表,遍歷鏈表找查看是有已經存在相同的key 10 for (Entry<K,V> e = table [i]; e != null; e = e. next) { 11 Object k; 12 // 經過對比hash值、key判斷是否已經存在相同的key 13 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 14 // 若是存在,取出當前key對應的value,供返回 15 V oldValue = e. value; 16 // 用新value替換之舊的value 17 e. value = value; 18 e.recordAccess( this); 19 // 返回舊value,退出方法 20 return oldValue; 21 } 22 } 23 24 // 若是不存在相同的key 25 // 修改版本+1 26 modCount++; 27 // 在數組i位置處添加一個新的鏈表節點 28 addEntry(hash, key, value, i); 29 // 沒有相同key的狀況,返回null 30 return null; 31 } 32 33 private V putForNullKey(V value) { 34 // 取出數組第1個位置(下標等於0)的節點,若是存在則覆蓋不存在則新增,和上面的put同樣很少講, 35 for (Entry<K,V> e = table [0]; e != null; e = e. next) { 36 if (e.key == null) { 37 V oldValue = e. value; 38 e. value = value; 39 e.recordAccess( this); 40 return oldValue; 41 } 42 } 43 modCount++; 44 // 若是key等於null,則hash值等於0 45 addEntry(0, null, value, 0); 46 return null; 47 }
增長和咱們上面分析的同樣,經過將key作hash取得一個散列值,將散列值對應到數組下標,而後將k-v組成鏈表節點存進數組中。數據結構
上面有三個方法須要重點關注,計算hash值的hash方法,計算數組索引位置的indexFor方法,添加新鏈表節點的addEntry方法,下面咱們逐一的看一下。app
1 /** 2 * Applies a supplemental hash function to a given hashCode, which 3 * defends against poor quality hash functions. This is critical 4 * because HashMap uses power -of- two length hash tables, that 5 * otherwise encounter collisions for hashCodes that do not differ 6 * in lower bits. Note: Null keys always map to hash 0, thus index 0. 7 */ 8 static int hash(int h) { 9 // This function ensures that hashCodes that differ only by 10 // constant multiples at each bit position have a bounded 11 // number of collisions (approximately 8 at default load factor). 12 h ^= (h >>> 20) ^ (h >>> 12); 13 return h ^ (h >>> 7) ^ (h >>> 4); 14 } 15 16 /** 17 * Returns index for hash code h. 18 */ 19 static int indexFor(int h, int length) { 20 return h & (length-1); 21 }
1 /** 2 * 增長一個k-v,hash組成的節點在數組內,同時可能會進行數組擴容。 3 */ 4 void addEntry( int hash, K key, V value, int bucketIndex) { 5 // 下面兩行行代碼的邏輯是,建立一個新節點放到單向鏈表的頭部,舊節點向後移 6 // 取出索引bucketIndex位置處的鏈表節點,若是節點不存在那就是null,也就是說當數組該位置處還未曾存放過節點的時候,這個地方就是null, 7 Entry<K,V> e = table[bucketIndex]; 8 // 建立一個節點,並放置在數組的bucketIndex索引位置處,並讓新的節點的next指向原來的節點 9 table[bucketIndex] = new Entry<K,V>(hash, key, value, e); 10 // 若是當前HashMap中的元素已經到達了臨界值,則將容量擴大2倍,並將size計數+1 11 if (size ++ >= threshold) 12 resize(2 * table.length ); 13 }
1 /** 2 * Rehashes the contents of this map into a new array with a 3 * larger capacity. This method is called automatically when the 4 * number of keys in this map reaches its threshold. 5 * 6 * If current capacity is MAXIMUM_CAPACITY, this method does not 7 * resize the map, but sets threshold to Integer.MAX_VALUE. 8 * This has the effect of preventing future calls. 9 * 10 * @param newCapacity the new capacity, MUST be a power of two; 11 * must be greater than current capacity unless current 12 * capacity is MAXIMUM_CAPACITY (in which case value 13 * is irrelevant). 14 */ 15 void resize( int newCapacity) { 16 // 當前數組 17 Entry[] oldTable = table; 18 // 當前數組容量 19 int oldCapacity = oldTable.length ; 20 // 若是當前數組已是默認最大容量MAXIMUM_CAPACITY ,則將臨界值改成Integer.MAX_VALUE 返回 21 if (oldCapacity == MAXIMUM_CAPACITY) { 22 threshold = Integer.MAX_VALUE; 23 return; 24 } 25 26 // 使用新的容量建立一個新的鏈表數組 27 Entry[] newTable = new Entry[newCapacity]; 28 // 將當前數組中的元素都移動到新數組中 29 transfer(newTable); 30 // 將當前數組指向新建立的數組 31 table = newTable; 32 // 從新計算臨界值 33 threshold = (int)(newCapacity * loadFactor); 34 } 35 36 /** 37 * Transfers all entries from current table to newTable. 38 */ 39 void transfer(Entry[] newTable) { 40 // 當前數組 41 Entry[] src = table; 42 // 新數組長度 43 int newCapacity = newTable.length ; 44 // 遍歷當前數組的元素,從新計算每一個元素所在數組位置 45 for (int j = 0; j < src. length; j++) { 46 // 取出數組中的鏈表第一個節點 47 Entry<K,V> e = src[j]; 48 if (e != null) { 49 // 將舊鏈表位置置空 50 src[j] = null; 51 // 循環鏈表,挨個將每一個節點插入到新的數組位置中 52 do { 53 // 取出鏈表中的當前節點的下一個節點 54 Entry<K,V> next = e. next; 55 // 從新計算該鏈表在數組中的索引位置 56 int i = indexFor(e. hash, newCapacity); 57 // 將下一個節點指向newTable[i] 58 e. next = newTable[i]; 59 // 將當前節點放置在newTable[i]位置 60 newTable[i] = e; 61 // 下一次循環 62 e = next; 63 } while (e != null); 64 } 65 } 66 }
1 /** 2 * 根據key刪除元素 3 */ 4 public V remove(Object key) { 5 Entry<K,V> e = removeEntryForKey(key); 6 return (e == null ? null : e. value); 7 } 8 9 /** 10 * 根據key刪除鏈表節點 11 */ 12 final Entry<K,V> removeEntryForKey(Object key) { 13 // 計算key的hash值 14 int hash = (key == null) ? 0 : hash(key.hashCode()); 15 // 根據hash值計算key在數組的索引位置 16 int i = indexFor(hash, table.length ); 17 // 找到該索引出的第一個節點 18 Entry<K,V> prev = table[i]; 19 Entry<K,V> e = prev; 20 21 // 遍歷鏈表(從鏈表第一個節點開始next),找出相同的key, 22 while (e != null) { 23 Entry<K,V> next = e. next; 24 Object k; 25 // 若是hash值和key都相等,則認爲相等 26 if (e.hash == hash && 27 ((k = e. key) == key || (key != null && key.equals(k)))) { 28 // 修改版本+1 29 modCount++; 30 // 計數器減1 31 size--; 32 // 若是第一個就是要刪除的節點(第一個節點沒有上一個節點,因此要分開判斷) 33 if (prev == e) 34 // 則將下一個節點放到table[i]位置(要刪除的節點被覆蓋) 35 table[i] = next; 36 else 37 // 不然將上一個節點的next指向當要刪除節點下一個(要刪除節點被忽略,沒有指向了) 38 prev. next = next; 39 e.recordRemoval( this); 40 // 返回刪除的節點內容 41 return e; 42 } 43 // 保存當前節點爲下次循環的上一個節點 44 prev = e; 45 // 下次循環 46 e = next; 47 } 48 49 return e; 50 }
1 public V get(Object key) { 2 // 若是key等於null,則調通getForNullKey方法 3 if (key == null) 4 return getForNullKey(); 5 // 計算key對應的hash值 6 int hash = hash(key.hashCode()); 7 // 經過hash值找到key對應數組的索引位置,遍歷該數組位置的鏈表 8 for (Entry<K,V> e = table [indexFor (hash, table .length)]; 9 e != null; 10 e = e. next) { 11 Object k; 12 // 若是hash值和key都相等,則認爲相等 13 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 14 // 返回value 15 return e.value ; 16 } 17 return null; 18 } 19 20 private V getForNullKey() { 21 // 遍歷數組第一個位置處的鏈表 22 for (Entry<K,V> e = table [0]; e != null; e = e. next) { 23 if (e.key == null) 24 return e.value ; 25 } 26 return null; 27 }
1 /** 2 * Returns <tt>true</tt> if this map contains a mapping for the 3 * specified key. 4 * 5 * @param key The key whose presence in this map is to be tested 6 * @return <tt> true</tt> if this map contains a mapping for the specified 7 * key. 8 */ 9 public boolean containsKey(Object key) { 10 return getEntry(key) != null; 11 } 12 13 /** 14 * Returns the entry associated with the specified key in the 15 * HashMap. Returns null if the HashMap contains no mapping 16 * for the key. 17 */ 18 final Entry<K,V> getEntry(Object key) { 19 int hash = (key == null) ? 0 : hash(key.hashCode()); 20 for (Entry<K,V> e = table [indexFor (hash, table .length)]; 21 e != null; 22 e = e. next) { 23 Object k; 24 if (e.hash == hash && 25 ((k = e. key) == key || (key != null && key.equals(k)))) 26 return e; 27 } 28 return null; 29 }
containsKey的代碼邏輯和get的代碼邏輯90%是相同的啊,爲何沒有封裝下呢?less
1 /** 2 * Returns <tt>true</tt> if this map maps one or more keys to the 3 * specified value. 4 * 5 * @param value value whose presence in this map is to be tested 6 * @return <tt> true</tt> if this map maps one or more keys to the 7 * specified value 8 */ 9 public boolean containsValue(Object value) { 10 if (value == null) 11 return containsNullValue(); 12 13 Entry[] tab = table; 14 // 遍歷整個table查詢是否有相同的value值 15 for (int i = 0; i < tab. length ; i++) 16 // 遍歷數組的每一個鏈表 17 for (Entry e = tab[i] ; e != null ; e = e.next) 18 if (value.equals(e.value )) 19 return true; 20 return false; 21 } 22 23 /** 24 * Special -case code for containsValue with null argument 25 */ 26 private boolean containsNullValue() { 27 Entry[] tab = table; 28 for (int i = 0; i < tab. length ; i++) 29 for (Entry e = tab[i] ; e != null ; e = e.next) 30 if (e.value == null) 31 return true; 32 return false; 33 }
能夠看到針對指定key的查找,因爲HashMap在結構上的優化,查找相對是十分高效的,而對於指定value的查找,要遍歷整個hash表,這樣是很是低效費時的。。。post
10.容量檢查性能
1 /** 2 * Returns the number of key -value mappings in this map. 3 * 4 * @return the number of key- value mappings in this map 5 */ 6 public int size() { 7 return size ; 8 } 9 10 /** 11 * Returns <tt>true</tt> if this map contains no key -value mappings. 12 * 13 * @return <tt> true</tt> if this map contains no key -value mappings 14 */ 15 public boolean isEmpty() { 16 return size == 0; 17 }