hashMap的底層實現是 數組+鏈表 的數據結構,數組是一個Entry<K,V>[] 的鍵值對對象數組,在數組的每一個索引上存儲的是包含Entry的節點對象,每一個Entry對象是一個單鏈表結構,維護這下一個Entry節點的引用;有點繞,用個圖來展現吧:java
Entry<K,V>[] 數組部分保存的是首個Entry節點;Entry節點包含一個 K值引用 V值引用 以及 引用下一個Entry 節點的next引用;數組
Entry節點的java代碼實現以下:數據結構
static class Entry<K,V> implements Map.Entry<K,V> { final K key; //key 引用 V value; //value 引用 Entry<K,V> next; //下一個Entry 節點的引用 }
下面再看下HashMap 對象的java實現代碼:app
包含的屬性有:源碼分析
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { /** * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * The load factor used when none specified in constructor. */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * An empty table instance to share when the table is not inflated. */ static final Entry<?,?>[] EMPTY_TABLE = {}; /** * The table, resized as necessary. Length MUST Always be a power of two. */ transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; /** * The number of key-value mappings contained in this map. */ transient int size; /** * The next size value at which to resize (capacity * load factor). * @serial */ // If table == EMPTY_TABLE then this is the initial capacity at which the // table will be created when inflated. int threshold; /** * The load factor for the hash table. * * @serial */ final float loadFactor;
}
比較重要的屬性是:this
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; 代表這是一個 Entry<K,V>[] 的數組類型;
下面看其無參的構造器:
public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); }
進入如下的構造方法:spa
public HashMap(int initialCapacity, float loadFactor) { //initialCapacity:16 loadFactor 0.75f if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) //MAXIMUM_CAPACITY 1073741824 false
initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) false throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; //賦值給loadFactor=0.75 threshold = initialCapacity; //賦值給threshold=16 當爲16是自動擴容 init(); }
下面再看看put(E e)的方法:code
public V put(K key, V value) { //如插入 key="city" value="shanghai" if (table == EMPTY_TABLE) { //true inflateTable(threshold); //參數爲16 } if (key == null) // false return putForNullKey(value); int hash = hash(key); //返回key值的hash碼; 好比返回爲 337 int i = indexFor(hash, table.length); //將hash 取模與16 得到的結果爲 1 for (Entry<K,V> e = table[i]; e != null; e = e.next) { //遍歷 Entry[1] 中的鏈表節點對象 包含 原先有的節點和新增進去的節點 Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //當Entry中包含 相同的hash碼的key 而且key和要添加的key相等便可以是否重複 則進入如下邏輯:新節點替換重複的節點 V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } }
下面是 inflateTable(threshold) 方法的源碼;對象
/** * Inflates the table. */ private void inflateTable(int toSize) { //toSize 16 // Find a power of 2 >= toSize int capacity = roundUpToPowerOf2(toSize); //capacity=16 threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); // threshold=16*0.75 table = new Entry[capacity]; //建立 Entry[] 數組長度爲16 initHashSeedAsNeeded(capacity); //這個方法能夠暫時不用深究 }
下面是 roundUpToPowerOf2(int i) 源碼blog
private static int roundUpToPowerOf2(int number) { // number=16 // assert number >= 0 : "number must be non-negative"; return number >= MAXIMUM_CAPACITY //fase 返回 16 ? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1; //number=16>1 返回 16 }
put 方法的源碼分析完了以後,接下來再看一下get(Object key) 的方法; 源碼:
public V get(Object key) { //如 key="name" if (key == null) //false return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); }
final Entry<K,V> getEntry(Object key) { //key=name if (size == 0) { //false return null; } int hash = (key == null) ? 0 : hash(key); //例如 返回hash=337 for (Entry<K,V> e = table[indexFor(hash, table.length)]; //indexFor(hash, table.length)上面分析過 返回值爲 1;遍歷 table[1] 中的節點 e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) //若是存在key的hash碼相等,而且對象也相等則返回 對應的Entry 節點 return e; } return null; //不然返回null }
到此,hashMap 的源碼基本分析完畢了,經過源碼分析咱們知道HashMap的底層是 數組+鏈表結構來存數數據的,添加節點存儲的位置是根據 key 取hash值 再取模於數組長度:返回的數值就是Entry接在在數組的哪一個位置;這種方式的存儲方式減小了存儲的時間和空間的複雜度;
知道了hashMap是由 數組+鏈表 的數據結構存儲數據後,咱們也很容易明白hashMap 的遍歷方式: