1. 程式人生 > >如何寫一個自己的HashMap

如何寫一個自己的HashMap

       想必很多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