Java複習筆記第二篇--集合學習之實現原理二
引言:上篇文章中我們學習了List和Set介面下面的集合的實現原理,這篇文章我們主要來學習Map介面下面的各個集合的實現原理。
1、HashMap
1.1、特性
- 採用鍵值對儲存
- 非執行緒安全的集合
- 允許值為null,只允許一個鍵為null
- 可以通過Collections.synchronizedMap將其變為執行緒安全的集合,或者使用ConcurrentHashMap代替。
- 初始容量為16,初始載入因子為0.75
1.2、實現原理
hash作為程式設計師使用最多的一種鍵值式的儲存集合;它的底層是通過陣列+連結串列+紅黑樹(JDK8優化之後);它根據鍵的hashCode值儲存資料,大多數情況下可以直接定位到它的值,因而具有很快的訪問速度,但遍歷順序卻是不確定的。HashMap非執行緒安全,即任一時刻可以有多個執行緒同時寫HashMap,可能會導致資料的不一致。如果需要滿足執行緒安全,可以用 Collections的synchronizedMap方法使HashMap具有執行緒安全的能力,或者使用ConcurrentHashMap。
- a、hashMap 的例項有兩個引數影響其效能:初始容量和載入因子
-
b、儲存資料的格式:通過一個靜態內部類Entry儲存資料;
static class Entry<K,V> implements Map.Entry<K,V>{
}
- c、載入因子:預設為0.75;
主要在擴容時起作用;一般也是擴容為原來的2倍
void resize(int newCapacity) {
Entry[] oldTable = table;
//獲取擴容之前的容量
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
//將原來陣列的元素拷貝到新的元素中;
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
- d、關於hash函式:
hashMap在儲存資料時會先根據它的key值呼叫一次hash函式;將得到的值作為真實的儲存資料的鍵;當然不排除兩個不同的key值產生相同的鍵值情況;我們稱之為hash衝突;常見的解決hash衝突的方式主要有如下:- 拉鍊法:將衝突的節點組織成一條連結串列
- 二次hash法:採用另外的雜湊函式對衝突結果進行處理的方法
- 開發地址法:
- 線性探測:從衝突的位置依次向下移動;
D = H(key);
ND = (D+di)%m; di取1,2,3,……,m-1 - 二次探測:
D = H(key);
ND = (D+di)%m; di取11,-11,22,-22,……,KK,-KK (K≤m/2) - 雙雜湊法
D = H1(key);
p = H2(key);
ND = (D+p)%m;
- 線性探測:從衝突的位置依次向下移動;
int hash = hash(key);
和之前一樣,為了方便深入理解hashmap這個資料結構,我們還是針對它的新增元素和刪除元素的方法進行詳細探討:
插入元素:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
①.判斷鍵值對陣列table[i]是否為空或為null,否則執行resize()進行擴容;
②.根據鍵值key計算hash值得到插入的陣列索引i,如果table[i]==null,直接新建節點新增,轉向⑥,如果table[i]不為空,轉向③;
③.判斷table[i]的首個元素是否和key一樣,如果相同直接覆蓋value,否則轉向④,這裡的相同指的是hashCode以及equals;
④.判斷table[i] 是否為treeNode,即table[i] 是否是紅黑樹,如果是紅黑樹,則直接在樹中插入鍵值對,否則轉向⑤;
⑤.遍歷table[i],判斷連結串列長度是否大於8,大於8的話把連結串列轉換為紅黑樹,在紅黑樹中執行插入操作,否則進行連結串列的插入操作;遍歷過程中若發現key已經存在直接覆蓋value即可;
⑥.插入成功後,判斷實際存在的鍵值對數量size是否超多了最大容量threshold,如果超過,進行擴容。
刪除元素
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
/**
* Implements Map.remove and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to match if matchValue, else ignored
* @param matchValue if true only remove if value is equal
* @param movable if false do not move other nodes while removing
* @return the node, or null if none
*/
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//判斷連結串列對應的位置是否為null
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
//是否已經形成連結串列
else if ((e = p.next) != null) {
//是否已經行為樹形結構
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
2、TreeMap
2.1、特性
- 插入的元素不能重複
- 插入的元素預設按照key有序(預設按照升序)
2.2、實現原理
底層通過紅黑樹實現的,
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left; // 左孩子
Entry<K,V> right;//右孩子
Entry<K,V> parent;// 父親節點
boolean color = BLACK;
和之前一樣我們還是來看看TreeMap的新增元素和刪除元素的方法來了解TreeMap的實現原理
新增元素:在樹種找到要插入的位置,即該節點的父親節點,然後根據comp的具體值決定是要插入到left還是right
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
刪除節點:本質上就是紅黑樹的節點刪除操作,想要理解TreeMap的刪除操作最好深入理解紅黑樹的刪除操作
public V remove(Object key) {
Entry<K,V> p = getEntry(key);
if (p == null)
return null;
V oldValue = p.value;
deleteEntry(p);
return oldValue;
}
/**
* Delete node p, and then rebalance the tree.
*/
private void deleteEntry(Entry<K,V> p) {
modCount++;
size--;
// If strictly internal, copy successor's element to p and then make p
// point to successor.
if (p.left != null && p.right != null) {
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
} // p has 2 children
// Start fixup at replacement node, if it exists.
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
// Link replacement to parent
replacement.parent = p.parent;
if (p.parent == null)
root = replacement;
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
// Null out links so they are OK to use by fixAfterDeletion.
p.left = p.right = p.parent = null;
// Fix replacement
if (p.color == BLACK)
fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node.
root = null;
} else { // No children. Use self as phantom replacement and unlink.
if (p.color == BLACK)
fixAfterDeletion(p);
if (p.parent != null) {
if (p == p.parent.left)
p.parent.left = null;
else if (p == p.parent.right)
p.parent.right = null;
p.parent = null;
}
}
}
參考文獻:
連結:https://zhuanlan.zhihu.com/p/21673805
http://alex09.iteye.com/blog/539549
相關文章
- Java 集合學習筆記Java筆記
- 【Java學習筆記】Collections集合Java筆記
- Java中的Map集合學習筆記Java筆記
- Java學習筆記記錄(二)Java筆記
- Redis基礎知識(學習筆記12--集合的底層實現原理)Redis筆記
- VC++深入詳解--之複習筆記(二)C++筆記
- ASP.NET Core 學習筆記 第二篇 依賴注入ASP.NET筆記依賴注入
- Java集合學習記錄——IteratorJava
- Java學習筆記之----------Java基本知識Java筆記
- 《JAVA學習指南》學習筆記Java筆記
- AQS原理學習筆記AQS筆記
- synchronized原理學習筆記synchronized筆記
- Linux堆管理實現原理學習筆記 (上半部)Linux筆記
- 拉鉤教育大前端學習筆記 --- Vite 實現原理前端筆記Vite
- 集合冪級數學習筆記筆記
- Vue學習筆記(二)------axios學習Vue筆記iOS
- JAVA學習筆記—JAVA WEB(二)JAVA WEB核心(下)Java筆記Web
- 【學習筆記】之:Java命名規範筆記Java
- JAVA學習筆記Java筆記
- Java IO學習筆記二:DirectByteBuffer與HeapByteBufferJava筆記
- TS學習筆記(二)筆記
- ANFIS學習筆記(二)筆記
- activiti學習筆記二筆記
- Typescript學習筆記(二)TypeScript筆記
- JavaScript學習筆記(二)JavaScript筆記
- Hibernate學習筆記二筆記
- React 學習筆記【二】React筆記
- TensorFlow學習筆記(二)筆記
- vue學習筆記二Vue筆記
- python學習筆記(二)Python筆記
- goLang學習筆記(二)Golang筆記
- 深度學習 DEEP LEARNING 學習筆記(二)深度學習筆記
- 容斥原理學習筆記筆記
- 網路學習筆記(二):TCP可靠傳輸原理筆記TCP
- Java IO學習筆記六:NIO到多路複用Java筆記
- python學習筆記24_集合set( )Python筆記
- java反射之動態代理學習筆記Java反射筆記
- Java學習筆記4Java筆記