如何寫一個自己的HashMap
阿新 • • 發佈:2018-12-10
想必很多Java工程師出去面試的時候都會被問到HashMap的底層實現原理,很多人覺得沒什麼必要,反正我會用就行,就我的感覺而言,在初期確實沒什麼必要,但是站在公司角度想,如果面試者連底層實現都搞定了,還怕一點表層應用的東西嗎?又或者說,我看到了另一個答案最能打動我:沉下去有多深,浮上來就有多高。
開始說一下必要的準備階段:
1.在寫自己的HashMap之初,我們需要對一定HashMap有一個初步的認識,比如HashMap底層是由陣列和連結串列實現的,具體的結構圖如圖所示:
還有比如說最大容量,初始容量,載入因子,紅黑樹等等概念
推薦幾個具體點的地址:
A.微信公眾號 --- 碼農翻身,搜尋HashMap就有
C.網易雲課堂搜尋HashMap,有一門只要一分錢的課程,40多分鐘,看完也可以有一個比較明確的瞭解
2.開始擼程式碼了
A.首先定義一個自己的Map介面,和連結串列介面:
public interface MyMapInterface<k,v> { // 大小 int size(); // 是否為空 boolean isEmpty(); // 根據key獲取元素 Object get(Object key); // 新增元素 Object put(Object key,Object value); // 內部介面 interface Entry<k,v>{ k getKey(); v getValue(); } }
B.編寫實現類:
public class MyHashMap<k,v> implements MyMapInterface { // 初始容量大小 --- 原始碼寫法 1 << 4 private final int DEFAULT_INITIAL_CAPACITY = 16; // 載入因子 private final float DEFAULT_LOAD_FACTOR = 0.75f; // 根據定義的靜態內部類,初始化連結串列,長度為預設長度 Node[] table = new Node[DEFAULT_INITIAL_CAPACITY]; // 長度 private int size = 0; @Override public int size() { return size; } @Override public boolean isEmpty() { return size == 0; } @Override public Object put(Object key, Object value) { // 計算key的hash值 int hashValue = hash(key); // 計算出應該存放的位置 int i = indexFor(hashValue,table.length); // 如果i處有資料且key一樣,進行覆蓋 for(Node node = table[i];node != null; node = node.next){ Object k; if(node.hash == hashValue && ((k = node.key)==key||key.equals(k))){ Object oldValue = node.value; node.value = value; return oldValue; } } // 如果i位置沒有資料,或i位置有資料,但key是新的key,新增節點 addEntry(key,value,hashValue,i); return null; } @Override public Object get(Object key) { // 根據物件的hashcode計算hash值 int hashValue = hash(key); // 根據hash值和連結串列長度,獲取插入位置的索引 int i = indexFor(hashValue,table.length); for(Node node = table[i];node != null;node = node.next){ if(node.key.equals(key) && hashValue == node.hash){ return node.value; } } return null; } // 向Entry新增元素 // hashvalue --- hash值 // i --- 索引位置 public void addEntry(Object key,Object value,int hashValue,int i){ // 如果超過了陣列約定長度,就擴容 if(++size >= table.length * DEFAULT_LOAD_FACTOR){ Node[] newTable = new Node[table.length << 1]; // 複製陣列 //System.arraycopy(table,0,newTable,0,table.length); transfer(table,newTable); table = newTable; } // 得到i處的資料 Node eNode = table[i]; // 新增節點,將該節點的next指向前一個節點 table[i] = new Node(hashValue,key,value,eNode); } // 引用JDK1.7的複製程式碼 public void transfer(Node[] src,Node[] newTable) { //src引用了舊的Entry陣列 int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { //遍歷舊的Entry陣列 Node e = src[j]; //取得舊Entry陣列的每個元素 if (e != null) { src[j] = null;//釋放舊Entry陣列的物件引用(for迴圈後,舊的Entry陣列不再引用任何物件) do { Node next = e.next; int i = indexFor(e.hash, newCapacity); //!!重新計算每個元素在陣列中的位置 e.next = newTable[i]; //標記[1] newTable[i] = e; //將元素放在陣列上 e = next; //訪問下一個Entry鏈上的元素 } while (e != null); } } } // 獲取插入的位置(取模運算 有瑕疵) public int indexFor(int hashValue,int length){ return hashValue % length; } // 獲取插入的位置,根據Obeject物件的hashcode 獲取hash值 public int hash(Object key){ int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } static class Node implements MyMapInterface.Entry{ // hash值 int hash; Object key; Object value; //指向下個節點(單鏈表) Node next; Node(int hash,Object key,Object value,Node next){ this.hash = hash; this.key = key; this.value = value; this.next = next; } @Override public Object getKey() { return key; } @Override public Object getValue() { return value; } } }
C.註釋還是很清楚的,在瞭解了一部分基礎知識的情況下,可以完全看動,至少我是這樣過來的,需要說明遇到的一個坑,在寫程式碼的初期,進行擴容操作的時候,我用的是System.arraycopy方法進行復制連結串列,但始終會出現莫名其妙的問題,因此我引入了JDK1.7的原始碼方法,transfer方法,進行一定的修改後,替換掉上面的複製方法,結果就正常了
D.測試類截圖:
如果有錯誤的地方,希望各位指出,需要互相交流的可以聯絡我,Q806857264