Jdk1.7 HashMap原始碼分析與思考
HashMap類圖
根據類圖可知,HashMap實現了三個介面繼承了一個抽象類。
實現的介面概覽
- 介面如下:
-
java.util.Map
其中Map介面中定義了Map基本操作方法,詳細介面描述請參考java.util.Map介面描述. Map介面中採用一個內部介面java.util.Map.Entry來封裝每一個鍵值對,這樣Map中的元素就變成了Map.Entry<K,V>的對映項。
-
java.lang.Cloneable
實現此介面可以對HashMap進行克隆, HashMap預設為淺克隆. 更多瞭解請點選: HashMap的clone方法(淺克隆) HashMap物件的深層克隆
繼承的抽象類概覽
- 抽象類如下
-
java.util.AbstractMap
AbstractMap 是 Map 介面的的實現類之一,也是 HashMap, TreeMap, ConcurrentHashMap 等類的父類。 Java 集合深入理解(15):AbstractMap
HashMap 成員變數
HashMap定義了13個成員變數。
成員變數1:DEFAULT_INITIAL_CAPACITY
/**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
此程式碼段定義HashMap初始容量大小為16,必須為2的冪,原始碼中使用了移位運算子計算出HashMap的初始容。 問題1: 為什麼原始碼中不直接寫成16而要使用移位運算呢?. 答案1: 我的理解是因為作業系統最終會使用二進位制進行計算,這樣寫省略了轉換過程,提高了效率。 問題2: 為什麼初始容量大小是16,而不是5(奇數代表),8(2的3次冪代表),10(偶數代表)?. 答案2: 待補充
成員變:2:MAXIMUM_CAPACITY
/**
* 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;
定義HashMap最大容量,如果使用者指定大於這個值,HashMap會使用該容量。1<<30 為2 ^ 30=1073741824。 問題1: 那麼為什麼定義是2^30 ?. 答案1: 待補充
成員變:3:DEFAULT_LOAD_FACTOR
/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
HashMap預設的負載因子,用於擴容。 問題1: 那麼為什麼定義是0.75f ?. 答案1: 待補充
成員變數4:EMPTY_TABLE
/**
* An empty table instance to share when the table is not inflated.
*/
static final Entry<?,?>[] EMPTY_TABLE = {};
定義共享的空表例項。 使用static final關鍵字修飾變數,說明此例項本身虛擬機器載入後放入執行時資料區的方法區,全域性共享,例項本身不可變,但是因為是容器型別的例項變數,所以容器中的物件是可以被修改的。
成員變數5:table
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
定義HashMap內部資料結構,型別為Entity,長度由容量指定。
成員變數:6:size
/**
* The number of key-value mappings contained in this map.
*/
transient int size;
HashMap容量大小變數 使用transient關鍵字說明此變數不需要序列化。
成員變數7:threshold
/**
* 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;
HashMap的極限容量,擴容臨界點(容量和載入因子的乘積)
成員變數8:loadFactor
/**
* The load factor for the hash table.
*
* @serial
*/
final float loadFactor;
hash table負載因子變數定義 使用final關鍵字修飾後,賦值一次,賦值後不能再被改變
成員變數9:modCount
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient int modCount;
HashMap修改的次數變數定義。
成員變數10:ALTERNATIVE_HASHING_THRESHOLD_DEFAULT
/**
* The default threshold of map capacity above which alternative hashing is
* used for String keys. Alternative hashing reduces the incidence of
* collisions due to weak hash code calculation for String keys.
* <p/>
* This value may be overridden by defining the system property
* {@code jdk.map.althashing.threshold}. A property value of {@code 1}
* forces alternative hashing to be used at all times whereas
* {@code -1} value ensures that alternative hashing is never used.
*/
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
預設的容器閥值大小。
成員變數11:hashSeed
/**
* A randomizing value associated with this instance that is applied to
* hash code of keys to make hash collisions harder to find. If 0 then
* alternative hashing is disabled.
*/
transient int hashSeed = 0;
雜湊種子(hashSeed),這個hashSeed用於優化雜湊函式,預設為0是不使用替代雜湊演算法,但是也可以自己去設定hashSeed的值,以達到優化效果。
成員變數12:entrySet
// Views
private transient Set<Map.Entry<K,V>> entrySet = null;
定義Map.Entry<K,V>集合。
成員變數13:serialVersionUID
private static final long serialVersionUID = 362498820763181265L;
隨機生成的序列化ID
HashMap四個建構函式
建構函式1: HashMap()
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
HashMap預設的建構函式,預設容量大小為16,負載因子為0.75,此兩個成員變數在上文中有詳細描述。
建構函式2:HashMap(int initialCapacity)
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
可以指定容量大小的HashMap構造,負載因子使用預設的0.75.
建構函式3: HashMap(Map<? extends K, ? extends V> m)
/**
* Constructs a new <tt>HashMap</tt> with the same mappings as the
* specified <tt>Map</tt>. The <tt>HashMap</tt> is created with
* default load factor (0.75) and an initial capacity sufficient to
* hold the mappings in the specified <tt>Map</tt>.
*
* @param m the map whose mappings are to be placed in this map
* @throws NullPointerException if the specified map is null
*/
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
//根據計算出來的擴容閥值初始化table,hashSeed, threshold
inflateTable(threshold);
//將傳入的Map資料迴圈放入此HashMap中
putAllForCreate(m);
}
建構函式4:HashMap(int initialCapacity, float loadFactor)
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public HashMap(int initialCapacity, float loadFactor) {
//如果容量小於0,則拋異常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//如果設定的容量大於定義的最大容量,則取最大容量值
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//如果負載因子小於0或者是非法值則拋異常
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)方法
將指定的值與此對映中的指定鍵相關聯。 如果對映先前包含鍵的對映,則替換舊值。原始碼如下:
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
//如果為陣列為空,則進行初始化操作。
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
//如果key為空,則將值value方到陣列0位置上,如果0位置有值則替換
return putForNullKey(value);
//計算key的hash值
int hash = hash(key);
//根據hash值和儲存資料的陣列長度計算位置索引
int i = indexFor(hash, table.length);
//查詢對應位置是否有值,如果有值則使用新值替換
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
//HashMap 空實現,可忽略
e.recordAccess(this);
return oldValue;
}
}
//記錄修改次數加1
modCount++;
//增加key-value對映到陣列中
addEntry(hash, key, value, i);
return null;
}
HashMap put方法流程圖
初始化陣列方法private void inflateTable(int toSize)
/**
* Inflates the table.
*/
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
//查詢大於toSize的最小2的冪數,例如傳入的toSize=23,那麼capacity為2^6=32
int capacity = roundUpToPowerOf2(toSize);
//根據容量和負載因子計算擴充套件閾值,當容量達到此閥值時,HashMap進行擴容。
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
//初始化EntiEntry<K,V>[] 陣列儲存大小
table = new Entry[capacity];
//初始化HashSeed
initHashSeedAsNeeded(capacity);
}
克隆和Map引數構造器中使用的關鍵方法private void putAllForCreate(Map<? extends K, ? extends V> m)方法
//迴圈遍歷傳入的Map集合,複製到新的Map中
private void putAllForCreate(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
putForCreate(e.getKey(), e.getValue());
}
/**
* This method is used instead of put by constructors and
* pseudoconstructors (clone, readObject). It does not resize the table,
* check for comodification, etc. It calls createEntry rather than
* addEntry.
*/
private void putForCreate(K key, V value) {
//根據鍵計算hash值,如果為空則hash值為0
int hash = null == key ? 0 : hash(key);
//根據hash值和表長度計算在陣列中的位置。
int i = indexFor(hash, table.length);
/**
* Look for preexisting entry for key. This will never happen for
* clone or deserialize. It will only happen for construction if the
* input Map is a sorted map whose ordering is inconsistent w/ equals.
*/
//1.如果當前儲存的陣列的位置存在資料,則替換新值
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
e.value = value;
return;
}
}
//2.否則插入資料
createEntry(hash, key, value, i);
}
HashMap擴容方法void addEntry(int hash, K key, V value, int bucketIndex)
/**
* Adds a new entry with the specified key, value and hash code to
* the specified bucket. It is the responsibility of this
* method to resize the table if appropriate.
*
* Subclass overrides this to alter the behavior of put method.
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
HashMap解決hash衝突方法void createEntry(int hash, K key, V value, int bucketIndex)
/**
* Like addEntry except that this version is used when creating entries
* as part of Map construction or "pseudo-construction" (cloning,
* deserialization). This version needn't worry about resizing the table.
*
* Subclass overrides this to alter the behavior of HashMap(Map),
* clone, and readObject.
*/
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}