前段時間研究了一下JDK 1.6 的 HashMap
源碼,把部份重要的方法分析一下,固然HashMap
中還有一些值得研究得就交給讀者了,若有不正確之處還望留言指正。java
須要熟悉數組和鏈表這兩個基本數據結構。若是對鏈表不太熟悉的話,能夠來幾道leetcode上的相關的鏈表算法題。熟悉後看 HashMap
就會快不少了。 基本原理:HashMap
中的基本數據結構是數組加鏈表。table
是一個固定的數組。 數組裏面的每一個坑裏面填的是一個叫Entry
類。 其實就是一個固定的Entry
數組。若是同一個坑裏面存在兩個不一樣的數據,那麼兩個數據就以鏈表的形式鏈接起來。最新的在最前面,緣由是認爲最新的容易常常被訪問。算法
基本原理知道了。如今直接研究帶參數的構造函數就能夠了,其餘的構造函數就是調用該方法。設計模式
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);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
複製代碼
MAXIMUM_CAPACITY = 1 << 30
2的30次方1073741824,也就是HashMap
中table
數組的大小不能超過該數字。 從上面代碼能夠看出來table
的坑位只能是2的冪次方。若是你傳入的initialCapacity
爲7 那麼其實table
的大小爲8; 也就是table
的大小爲傳入進來的initialCapacity
的數值大於該大小的2的冪次方。threshold
爲他的閾值也就是 HashMap 的真正大小不能超過該值,超過了就進行擴容操做。 若是table
數組的大小爲16時。用它默認的擴容因子0.75f。那麼他的閾值就是12。 也就是 table
數據,數組中的加上鍊表的不能超過12。 咱們看看第二個構造函數。參數爲一個Map
我這裏順便把HashMap
中的嵌套類Entry
類說一下。能夠本身再源碼上觀看。數組
public HashMap(Map<? extends K, ? extends V> m) {
// 對比該map的size大小,新的map最新的容量爲16
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
// 建立全部map
putAllForCreate(m);
}
private void putAllForCreate(Map<? extends K, ? extends V> m) {
// 對每個Entry進行迭代
for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {
Map.Entry<? extends K, ? extends V> e = i.next();
//建立數據賦值
putForCreate(e.getKey(), e.getValue());
}
}
private void putForCreate(K key, V value) {
int hash = (key == null) ? 0 : hash(key.hashCode());
// 計算table中的位置
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. */
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;
}
}
// 建立Entry
createEntry(hash, key, value, i);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
// 頭節點插入
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
size++;
}
// 嵌套類 和HashMap類不要緊 獨立存在 默認權限 只能本包訪問 也就是Java.util下的包訪問 HashHap中並無提供 Map.Entry<K,V>這樣的返回對象出去。有的只是一個 Set<Map.Entry<K,V>>
//一個代理類。
static class Entry<K,V> implements Map.Entry<K,V> {
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;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return (key==null ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
/** * This method is invoked whenever the value in an entry is * overwritten by an invocation of put(k,v) for a key k that's already * in the HashMap. */
void recordAccess(HashMap<K,V> m) {
}
/** * This method is invoked whenever the entry is * removed from the table. */
void recordRemoval(HashMap<K,V> m) {
}
}
複製代碼
爲何要從put
方法研究起呢。由於HashMap
中最經常使用得就是put
方法。並且裏面還涉及到擴容操做。若是把這些看懂了仍是會很舒服得。數據結構
public V put(K key, V value) {
if (key == null)
// 若是key爲null的話 直接添加到table[0]的位置 for 循環 table[0]上的元素。若是有元素的話 查看該元素的key是否是null 若是是的話 就更新value值,直到table[0]這個鏈表結束。 若是結束後仍是沒有的話,就把爲null的key 對應的value 頭插法 插入頭部。 能夠查看 putForNullKey(value) 方法。
return putForNullKey(value);
// 計算Hash值
int hash = hash(key.hashCode());
// 取key的Hash值得 二進制數得後幾位。 若是key得hash爲1101011 。而table這個數組得大小一直都是2的冪次方。 indexFor()方法作的事 key的hash與table.length-1作&運算。假如table數組的大小爲16,也就是 11011011 & 1111 會等於 1011 。這個方法的意義也就是隻要你得Hash值是隨機的,碰撞性低,那麼你在table中位置也就是 碰撞低的。
int i = indexFor(hash, table.length);
// 查詢該table[i] 位置上的鏈表。
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 若是 key相等 那麼就更新 不然 下一位。。。。 直至結束。
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 修改次數加一
modCount++;
// 頭插法 並看size是都大於閾值了,若是大於就要擴容了。
addEntry(hash, key, value, i);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
//擴容操做 2倍擴容
resize(2 * table.length);
}
// 擴容方法 參數爲擴容大小
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
// 建立一個新得數組 名字叫作newTable length爲 newCapacity
Entry[] newTable = new Entry[newCapacity];
// 擴容操做
transfer(newTable);
// 從新賦值
table = newTable;
// 閾值
threshold = (int)(newCapacity * loadFactor);
}
// 擴容操做
void transfer(Entry[] newTable) {
// 將原先的table數組 賦值給 src
Entry[] src = table;
int newCapacity = newTable.length;
// 逐個操做 從 src[0] 位置上的Entry 開始
for (int j = 0; j < src.length; j++) {
// 將src[j]的值給 e變量。
Entry<K,V> e = src[j];
// 對這個e 鏈表進行往下操做
if (e != null) {
// 清空
src[j] = null;
do {
//e 的下面一位 其實就是 next 後移 (這裏若是兩個線程同時在這裏操做的話,A線程在這裏執行這條語句後掛起的話,B線程完成擴容操做後,A線程再喚醒時,有可能發生循環鏈表。而後使用get方法的時候,致使死循環,cpu利用100%)
Entry<K,V> next = e.next;
// 對e 從新定位。
int i = indexFor(e.hash, newCapacity);
// 將e.next 從e 斷開 並把e.next的值 指到 newTable[i]的值
e.next = newTable[i];
// 將 e 賦值給 newTable[i]
newTable[i] = e;
// e 日後移
e = next;
} while (e != null);
}
}
}
複製代碼
舒服了舒服了。 若是想看怎麼發生死循環的能夠看小灰的文章 高併發下的HashMap 。併發
get方法相對而言就比較簡單了。app
public V get(Object key) {
if (key == null)
// 直接查詢table[0] 上鍊表key爲 null的值
return getForNullKey();
// 定位table上的位置
int hash = hash(key.hashCode());
// 鏈表的查詢
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.equals(k)))
return e.value;
}
return null;
}
複製代碼
remove方法相對而言,只要你會鏈表的刪除操做,就很好理解了。若是有不明白的能夠。將鏈表這個數據結構好好學習一下。函數
public V remove(Object key) {
// 移除元素方法
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
// 這裏其實就是鏈表的刪除操做 。
final Entry<K,V> removeEntryForKey(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode());
// 定位位置
int i = indexFor(hash, table.length);
// 將table[i] 這個鏈表賦值給prev
Entry<K,V> prev = table[i];
// prev 賦值給 e
Entry<K,V> e = prev;
while (e != null) {
// 下面一位
Entry<K,V> next = e.next;
Object k;
// key是否相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
// 若是要刪除的時table[i]的頭部數據
if (prev == e)
// table[i] 等於next 刪除頭部
table[i] = next;
else
// 不然 刪除這個
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
複製代碼
HashMap
中的學問,遠不止這些。 其中還涉及到設計模式,迭代器等等。上面這些只是經常使用的。我的很是推薦把數組和鏈表這個兩個很是基礎的數據結構好好練習一下。雖說早就把JDK 1.6的HashMap
源碼看了一下,順便把 ConcurrentHashMap
中的一些源碼也看了。可是寫下來的時候,再看一遍,印象果真深入多了。先把1.6的看了,在看1.8的吧。高併發