一、弄清楚HashMap 之前,先回答以下幾個問題
1、HashMap 是執行緒安全的嗎?
2、HashMap 資料結構是什麼?
- 陣列、連結串列、紅黑樹
3、JDK8 對 HashMap 優化了哪塊,為何要優化?
二、逐步來認識HashMap
1、陣列
public class Array {
/**
* 刪除 插入 慢 O(n)
* 找到下標的查詢 O(1)
* java.util.ArrayList
* @param args
*/
public static void main(String[] args) {
Integer[] integers = new Integer[10];
integers[0] = 0;
integers[1] = 1;
integers[2] = 2;
integers[3] = 3;
integers[4] = 4;
}
}
複製程式碼
陣列:採用一段連續的儲存單元來儲存資料。對於指定下標的查詢,時間複雜度為O(1); 對於一般的插入刪除操作,涉及到陣列元素的移動,其平均複雜度為O(n)。
2、線性連結串列
public class Node {
public Node next;
private Object data;
public Node(Object data){
this.data = data;
}
/**
* 新增、插入 時間複雜度O(1)
* 查詢時間複雜度O(n)
* java.util.LinkedList
* @param args
*/
public static void main(String[] args) {
Node node = new Node(1);
node.next = new Node(2);
node.next.next = new Node(3);
}
}
複製程式碼
線性連結串列:對於連結串列的新增、刪除等操作(在找到指定操作位置後),僅需要處理結點間的引用即可,時間複雜度為O(1),而查詢操作需要遍歷連結串列逐一進行比對,複雜度為O(n)。
3、雜湊hash
雜湊演算法(也叫雜湊),就是把任意長度值(key)通過雜湊演算法變成固定長度的key地址,通過這個地址進行訪問的資料結構。
它通過把關鍵碼值對映到表中一個位置來訪問記錄,以加快查詢的速度。
這個對映的函式也叫做雜湊函式,存放記錄的陣列叫做雜湊表。
4、理解HashMap 技術本質
public class App {
public static void main(String[] args) {
//Map<String,String> map = new HashMap();
App map = new App();
map.put("劉一","劉一");
map.put("陳二","陳二");
map.put("張三","張三");
map.put("李四","李四");
map.put("王五","張一");
map.put("悟空","悟空");
}
/**
* hash 演算法 山寨
* @param key
* @param value
*/
public void put(String key, String value){
// HashMap預設陣列長度為16
System.out.printf("key:%s,hash值:%s,儲存位置:%s\r\n",key,key.hashCode(),Math.abs(key.hashCode() % 6));
}
複製程式碼
輸出結果:
key:劉一,hash值:671464,儲存位置:4
key:陳二,hash值:1212740,儲存位置:5
key:張三,hash值:774889,儲存位置:4
key:李四,hash值:842061,儲存位置:6
key:王五,hash值:937065,儲存位置:0
key:悟空,hash值:798139,儲存位置:4
複製程式碼
分析HashMap 技術本質:
5、手寫實現
定義介面:
/**
* 定義一個介面
* @param <K>
* @param <V>
*/
public interface Map<K,V> {
public V put(K k,V v);
public V get(K k);
public int size();
public interface Entry<K,V>{
public K getKey();
public V getValue();
}
}
複製程式碼
介面實現:
/**
* <p>
* 實現HashMap
* </p>
*
* @author: org_hejianhui@163.com
* @create: 2019-04-26 22:52
* @see HashMap
* @since JDK1.8
*/
public class HashMap<K, V> implements Map<K, V> {
private Entry<K, V>[] tables = null;
private static int defaultLengh = 16;
private int size = 0;
public HashMap() {
this.tables = new Entry[defaultLengh];
}
public V put(K k, V v) {
// hash 出來的hash值
int index = hash(k);
// 陣列的長度 index 值 下標的位置
Entry entry = tables[index];
if (entry == null) {
tables[index] = new Entry<>(k, v, null, index);
size++;
} else {
tables[index] = new Entry<>(k, v, entry, index);
}
// 通過位置找到我們的table對應的Entry
return tables[index].getValue();
}
public V get(K k) {
if (size == 0)
return null;
// hash 出來的值
int index = hash(k);
Entry<K, V> entry = getEntry(k, index);
return entry == null ? null : entry.getValue();
}
public int size() {
return size;
}
class Entry<K, V> implements Map.Entry<K, V> {
private K k;
private V v;
private Entry next;
private int hash;
public Entry(K k, V v, Entry next, int hash) {
this.k = k;
this.v = v;
this.next = next;
this.hash = hash;
}
public K getKey() {
return k;
}
public V getValue() {
return v;
}
}
private int hash(K k) {
int index = k.hashCode() & (defaultLengh - 1);
return Math.abs(index);
}
private Entry<K, V> getEntry(K k, int index) {
for (Entry e = tables[index]; e != null; e = e.next) {
if (e.hash == index && (e.getKey() == k || e.getKey().equals(k))) {
return e;
}
}
return null;
}
}
複製程式碼
測試驗證:
public static void main(String[] args) {
//Map<String,String> map = new HashMap();
//App map = new App();
com.nuih.map.Map map = new com.nuih.map.HashMap();
map.put("劉一","劉一");
map.put("陳二","陳二");
map.put("張三","張三");
map.put("李四","李四");
map.put("王五","張一");
map.put("悟空","悟空");
System.out.println(map.get("悟空"));
}
複製程式碼
6、現在回答一下JDK8 對 HashMap 優化了哪塊,為何要優化?
- 原因是因為連結串列過長,解決平衡性,JDK8 引入紅黑樹來解決連結串列查詢的速度,但同樣也帶來一個問題:插入變慢,它預設有個閥值預設8才會轉變成紅黑樹,原始碼如下: