Map
1. HashMap 是根據數組加單向鏈表來實現的
2. 數組中存儲的就是Node對象
鏈表節點類
// hash 哈希值
// key key信息
// value value信息
// next 下一個節點
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
put流程
- 調用put(key,value) 方法像map裏添加數據
- 根據key生成hashcode,根據hashcode,key,value來創建創建Node節點對象
- hashcode和(數組大小(默認初始16)-1)根據‘&’運算得到數組下標位置。所以HashMap是無序的
// 源碼629行
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
- 如果此下標位置上已經存在Node對象。則根據使用鏈表綁定。(此下標下最後一個節點的next屬性指向此節點的地址)。key的hashcode一致則覆蓋。
// 源碼 641行
p.next = newNode(hash, key, value, null);
- 當數組使用率超過(數組大小*負載因子)時,數組擴容一倍。並數組下的Node節點重新排位。
get流程
- 調用get(key)方法
- 根據key生成hashcode,hashcode和(數組大小(默認初始16)-1)根據‘&’運算得到數組下標位置。
- 根據下標得到數組下的Node節點對象
// 源碼568行
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
- 如果此節點的key不是我們想要的key,那麼根據此節點next屬性向下查找,一致查找到對的Node節點
- 返回此節點的value值
HashMap 和 HashTable 有這樣幾個屬性
threshold:數組大小乘以負載因子得到的值
loadFactor :負載因子(默認0.75)
size: 數組使用率
- 數組容量默認16
- 負載因子默認0.75
- threshold 等於 數組容量乘以負載因子,默認(16 * 0.75 = 12)。
- 當put() 時, size 大於 threshold,數組擴容,擴容爲 (原容量 << 1)
- 也就是說:(默認)當size大於12時,數組進行擴容至32,threshold變成24。當size大於24時,數組擴容至64,threshold變成48。
HashMap和HashTable有什麼區別
1、HashMap是非線程安全的,HashTable的操作方法都被synchronized修飾所以是線程安全的。
2、HashMap的鍵和值都允許有null值存在,而HashTable則不行。
3、因爲線程安全的問題,HashMap效率比HashTable的要高。
LinkedHashMap
LinkedHashMap裏的屬性
// 第一個(第一個添加的對象的引用)
transient LinkedHashMap.Entry<K,V> head;
// 最後一個(最後一個添加的對象的引用)
transient LinkedHashMap.Entry<K,V> tail;
- LinkedHashMap是集成HashMap實現的
- LinkedHashMap 重寫了 HashMap的newNode方法,此方法重新創建了node節點。新創建的node節點繼承了HashMap的Node的類,額外還擁有的before(上一個) 和 after(下一個)。而這兩個屬性保證了Map的有序性(雙向鏈表)。
實現過程
-
當LinkedHashMap調用put(key,value) 時,會創建Node對象,LinkedHashMap重寫了newNode()和Node類。當創建Node時。
-
如果是第一次put就將Node的地址分別賦給head和tail第二次put時,tail(前一個節點對象) 的 after的值設置爲當前創建的對象。當前的對象的before設置爲tail(前一個節點對象),tail的地址在指向當前節點對象。詳細信息看下面代碼
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
這樣LinkedHashMap 相當於是一個雙向鏈表和HashMap的結合
Map的遍歷
Map<String,String> map = new HashMap<>();
map.put("name","劉志強");
map.put("age","24");
map.forEach((k,v)-> System.out.println(k + ":" + v));
// 或者(jdk1.8生效)
Set<Map.Entry<String, String>> set = map.entrySet();
Iterator<Map.Entry<String, String>> value = set.iterator();
while (value.hasNext()) {
Map.Entry s = value.next();
System.out.println(s.getKey() + ":" + s.getValue());
}
TreeMap
TreeMap中的元素默認按照keys的自然排序排列。
(對Integer來說,其自然排序就是數字的升序;對String來說,其自然排序就是按照字母表排序)