春秋招java後端方向技能全面突破-基礎篇05
HashMap
首先要說的是HashMap 是一個散列表,它儲存的內容是鍵值對(key-value)對映。同時HashMap 的實現不是同步的,這也就是意味著它不是執行緒安全的。它的key、value都可以為null。此外,HashMap中的對映也不是有序的(由於沒有實現了NavigableMap介面,對於NavigableMap來說,它是一個可導航的鍵-值對集合)。
對於Hashmap來說,它是存在兩個引數會影響其效能,“初始容量”(這裡得容量是指雜湊表中桶的數量,初始容量是表在建立時的容量)和“載入因子”(雜湊表在達到自動增加之前所能達到最大的尺度),當雜湊表的數目超過當前容量和載入因子乘積的時候,就要對其進行重建表結構,即rehash,使雜湊表變成大約兩倍的桶數。
整體的一個實現結構如下:(!!這裡要特別注意它的連結串列為單向連結串列)
HashMap是通過"拉鍊法"實現的雜湊表。它包括幾個重要的成員變數:table, size, threshold, loadFactor, modCount。
table是一個Entry[]陣列型別,而Entry實際上就是一個單向連結串列。雜湊表的"key-value鍵值對"都是儲存在Entry陣列中的。
size是HashMap的大小,它是HashMap儲存的鍵值對的數量。
threshold是HashMap的閾值,用於判斷是否需要調整HashMap的容量。threshold的值="容量*載入因子",當HashMap中儲存資料的數量達到threshold時,就需要將HashMap的容量加倍。
loadFactor就是載入因子。
modCount是用來實現fail-fast機制的。
這裡提一下資料節點Entry的資料結構:
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; // 指向下一個節點 Entry<K,V> next; final int hash; // 建構函式。 // 輸入引數包括"雜湊值(h)", "鍵(k)", "值(v)", "下一節點(n)" 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; } // 判斷兩個Entry是否相等 // 若兩個Entry的“key”和“value”都相等,則返回true。 // 否則,返回false 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; } // 實現hashCode() public final int hashCode() { return (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); } public final String toString() { return getKey() + "=" + getValue(); } }
而對於拉鍊發解決Hash衝突,則為以下步驟:
- 得到一個
key
- 計算
key
的hashValue
- 根據
hashValue
值定位到data[hashValue]
。(data[hashValue]
是一條連結串列) - 若
data[hashValue]
為空則直接插入 - 不然則新增到連結串列末尾
再說說HashMap的方法,clear()(清空HashMap。它是通過將所有的元素設為null來實現的),containsKey()(判斷HashMap是否包含key,containsKey() 首先通過getEntry(key)獲取key對應的Entry,然後判斷該Entry是否為null,這裡需要強調的是:HashMap將“key為null”的元素都放在table的位置0處,即table[0]中;“key不為null”的放在table的其餘位置!
)
這裡提兩個方法:
get() 的作用是獲取key對應的value:
public V get(Object key) {
if (key == null)
return getForNullKey();
// 獲取key的hash值
int hash = hash(key.hashCode());
// 在“該hash值對應的連結串列”上查詢“鍵值等於key”的元素
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;
}
put() 的作用是對外提供介面,讓HashMap物件可以通過put()將“key-value”新增到HashMap中:
public V put(K key, V value) {
// 若“key為null”,則將該鍵值對新增到table[0]中。
if (key == null)
return putForNullKey(value);
// 若“key不為null”,則計算該key的雜湊值,然後將其新增到該雜湊值對應的連結串列中。
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 若“該key”對應的鍵值對已經存在,則用新的value取代舊的value。然後退出!
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 若“該key”對應的鍵值對不存在,則將“key-value”新增到table中
modCount++;
addEntry(hash, key, value, i);
return null;
}
最後提一下HashMap的遍歷方式:
1 遍歷HashMap的鍵值對
第一步:根據entrySet()獲取HashMap的“鍵值對”的Set集合。
第二步:通過Iterator迭代器遍歷“第一步”得到的集合。
2 遍歷HashMap的鍵
第一步:根據keySet()獲取HashMap的“鍵”的Set集合。
第二步:通過Iterator迭代器遍歷“第一步”得到的集合。
3 遍歷HashMap的值
第一步:根據value()獲取HashMap的“值”的集合。
第二步:通過Iterator迭代器遍歷“第一步”得到的集合。
TreeMap
首先我們應該知道TreeMap 是一個有序的key-value集合,它是通過紅黑樹(該對映根據其鍵的自然順序進行排序,或者根據建立對映時提供的 Comparator 進行排序,具體取決於使用的構造方法,具體紅黑樹內容我會在後續的資料結構中詳細講的)實現的,對於我們提及的R-B Tree,它包含幾個重要的成員變數: root, size, comparator。
root 是紅黑數的根節點。它是Entry型別,Entry是紅黑數的節點,它包含了紅黑數的6個基本組成成分:key(鍵)、value(值)、left(左孩子)、right(右孩子)、parent(父節點)、color(顏色:Red-false Black-ture)。Entry節點根據key進行排序,Entry節點包含的內容為value,紅黑數排序時,根據Entry中的key進行排序;Entry中的key比較大小是根據比較器comparator來進行判斷的,size是紅黑數中節點的個數。(由於後端面試中TreeMap提及的不是特別多,手撕紅黑AVL之類就有點過分了,所以這裡就不多提及了)
HashSet
首先明確一點,不管是HashSet還是TreeSet都是繼承自Map的,比如繼承自HashMap的HashSet具有了非同步(!!如果多個執行緒同時訪問一個雜湊 set,而其中至少一個執行緒修改了該 set,那麼它必須 保持外部同步。這通常是通過對自然封裝該 set 的物件執行同步操作來完成的。如果不存在這樣的物件,則應該使用 Collections.synchronizedSet 方法來“包裝” set。最好在建立時完成這一操作,以防止對該 set 進行意外的不同步訪問)的特點,且可以為null值,同時Set還具有無序的特點。
TreeSet(非執行緒安全)
TreeSet 是一個有序的集合,它的作用是提供有序的Set集合。它繼承於AbstractSet抽象類,實現了NavigableSet<E>, Cloneable, java.io.Serializable介面。(!!這裡注意一下,TreeSet中的元素支援2種排序方式:自然排序 或者 根據建立TreeSet 時提供的 Comparator 進行排序。這取決於使用的構造方法。)