1. 程式人生 > 實用技巧 >建議收藏!細說HashMap實現,Hash衝突模擬思路講解。

建議收藏!細說HashMap實現,Hash衝突模擬思路講解。

思路記錄:

1.HashMap主體是一個Entry陣列,當多個key值經過hash計算得到的索引一致,則發生Hash衝突,該索引上的Entry物件就會成為連結串列的頭結點,後續同索引的Entry將會插入該連結串列。

2.當進行刪除時,應考慮如果目標物件在連結串列中且還有子節點,就需要按照連結串列的刪除法進行,防止後續物件丟失。假設father為目標物件的前一個結節。則father.next = father.next.next

3.擴容對於新舊兩個陣列以及索引需要理清思路,並且在putgetremove的程式碼裡分清值傳遞與引用傳遞,防止自以為物件追加進連結串列其實沒有。

4.程式碼每個關鍵處都有註釋,閱讀前需對HashMap的基本特點心中有大概的理解。


程式碼實現:

介面類

對於每個操作的hashConfictIndex引數作用,因為能引起hash衝突的情況比較難找到,所以追加了這個引數,傳入-1就按正常情況呼叫hash方法對陣列規模取餘計算索引,如果我們想給索引5的位置追加物件來模仿雜湊衝突,就需要傳入5來模擬。這種模擬的物件在getremove時也需要我們手動傳入同index,因為正常的hash計算根據我們給的key並不一定能求得我們插入時設定的索引。

package com.lx.myhashmap;

public interface MyHashMapInter {

    public String get(String key,int hashConfictIndex);

    public void put(String key,String value,int hashConfictIndex);

    public void remove(String key,int hashConfictIndex);

    interface MyEntryInter{
        public String get();

        public void set();
    }
}

實現類

在每個關鍵處都有註釋以及控制檯輸出,如果看著不清楚可以複製程式碼執行跟斷點走,記得修改包名com.xx.xxx

package com.lx.myhashmap;

import java.util.Map;

public class MyHashMap implements MyHashMapInter {

    //初始容量
    private int initCapacity;

    //已放入元素
    public int counts;

    //Entry陣列
    MyEntry[] table;

    //擴容係數
    private double loadFactory = 0.75;

    public MyHashMap(int initCapacity, double loadFactory) {
        this.initCapacity = initCapacity;
        this.loadFactory = loadFactory;

        table = new MyEntry[initCapacity];
    }

    @Override
    public String get(String key, int hashConflictIndex) {

        int index;

        if (-1 == hashConflictIndex) {
            index = hash(key) % initCapacity;
        } else {
            index = hashConflictIndex;
        }

        //陣列該索引處空
        if (table[index] == null) {
            return null;
        }

        //key相同
        if (table[index].key.equals(key)) {
            return table[index].value;
        }

        //遍歷連結串列
        MyEntry temp = table[index];

        while (temp.next != null) {
            temp = temp.next;

            if (temp.key.equals(key)) {
                return temp.value;
            }
        }

        return null;
    }

    @Override
    public void put(String key, String value, int hashConflictIndex) {

        int index;

        //計算該key索引
        if (-1 == hashConflictIndex) {
            index = hash(key) % initCapacity;
        } else {
            index = hashConflictIndex;
        }

        System.out.println("計算得到索引為  " + index);

        if (null == table[index]) {//如果該索引上無物件
            table[index] = new MyEntry(key, value, null);
            counts++;
            reLoad();
            System.out.println("put: 在陣列中新增");
        } else {//如果已有物件

            //如果key相同
            if (table[index].key.equals(key)) {
                table[index].value = value;
                System.out.println("put: 覆蓋陣列物件");
            } else {//遍歷連結串列
                System.out.println("put: 進入連結串列");
                MyEntry temp = table[index];
                MyEntry father = temp;
                while (temp.next != null) {

                    father = temp;
                    temp = temp.next;

                    if (temp.key.equals(key)) {
                        temp.value = value;
                        System.out.println("put: 連結串列物件覆蓋");
                        return;
                    }
                }

                temp.next = new MyEntry(key, value, null);
                counts++;
                reLoad();
                System.out.println("put: 連結串列尾追加");
            }
        }

    }

    @Override
    public void remove(String key, int hashConflictIndex) {

        int index;

        if (-1 == hashConflictIndex) {
            index = hash(key) % initCapacity;
        } else {
            index = hashConflictIndex;
        }

        if (table[index] == null) {
            System.out.println("remove:沒有該值");
        }

        if (table[index].key.equals(key)) {

            if (table[index].next == null) {
                table[index] = null;
                System.out.println("remove: 陣列中找到了 刪除");
                return;
            } else {
                table[index] = table[index].next;
                System.out.println("remove: 找到了 刪除 該鏈下一元素補位");
                return;
            }
        }

        MyEntry temp = table[index];

        while (temp.next != null) {

            MyEntry father = temp;

            temp = temp.next;

            if (temp.key.equals(key)) {

                if (temp.next == null) {
                    father.next = null;

                    System.out.println("remove: 連結串列中已經找到,刪除");
                } else {
                    father.next = father.next.next;
                    System.out.println("remove: 連結串列中已經找到,刪除並連結兩端");
                }

                return;
            }
        }

        System.out.println("remove: 已遍歷完連結串列,沒有該值");
    }

    /*
        雜湊方法
         */
    private int hash(String key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    private void reLoad() {

        double top = initCapacity * loadFactory;

        if (counts >= top) {
            System.out.println("reload: 達到閾值,擴容  ");

            initCapacity = 2 * initCapacity;

            MyEntry[] oldTable = table;

            table = new MyEntry[initCapacity];

            counts = 0;

            for(int i = 0; i < oldTable.length; i++) {

                MyEntry entry = oldTable[i];
                while (entry != null) {

                    put(entry.key, entry.value, -1);

                    entry = entry.next;
                }
            }
        }

    }

    class MyEntry implements MyEntryInter {

        public String key;

        public String value;

        public MyEntry next;

        public MyEntry(String key, String value, MyEntry next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }

        @Override
        public String get() {
            return value;
        }

        @Override
        public void set() {
            this.value = value;
        }
    }
}


測試案例

1.陣列覆蓋

程式碼:

package com.lx.myhashmap;

import java.util.HashMap;

public class Demo {
    public static void main(String[] args) {

        MyHashMap map = new MyHashMap(8,0.75);

        map.put("1","hello",-1);
        System.out.println("------------------------------");

        System.out.println("值為:"+map.get("1",-1)+"\n----------------");

        map.put("1","world",-1);
        System.out.println("------------------------------");

        System.out.println("值為:"+map.get("1",-1)+"\n----------------");
    }
}

輸出:

計算得到索引為  1
put: 在陣列中新增
------------------------------
值為:hello
----------------
計算得到索引為  1
put: 覆蓋陣列物件
------------------------------
值為:world
----------------

2.連結串列追加

這裡我們不傳-1,而是傳入索引1來模擬雜湊衝突(假設也計算出了1)

程式碼:

package com.lx.myhashmap;

import java.util.HashMap;

public class Demo {
    public static void main(String[] args) {

        MyHashMap map = new MyHashMap(8,0.75);

        map.put("1","hello",-1);
        System.out.println("------------------------------");

        System.out.println("值為:"+map.get("1",-1)+"\n----------------");

        map.put("3","world",1);
        System.out.println("------------------------------");

        System.out.println("值為:"+map.get("3",1)+"\n----------------");
    }
}

輸出:

計算得到索引為  1
put: 在陣列中新增
------------------------------
值為:hello
----------------
計算得到索引為  1
put: 進入連結串列
put: 連結串列尾追加
------------------------------
值為:world
----------------

3.擴容

程式碼:

package com.lx.myhashmap;

import java.util.HashMap;

public class Demo {
    public static void main(String[] args) {

        MyHashMap map = new MyHashMap(8,0.75);

        map.put("1","hello",-1);
        map.put("2","hello",-1);
        map.put("3","hello",1);
        map.put("4","hello",1);
        map.put("5","hello",-1);
        map.put("6","hello",-1);
        map.put("7","hello",-1);
    }
}

輸出:

計算得到索引為  1
put: 在陣列中新增
計算得到索引為  2
put: 在陣列中新增
計算得到索引為  1
put: 進入連結串列
put: 連結串列尾追加
計算得到索引為  1
put: 進入連結串列
put: 連結串列尾追加
計算得到索引為  5
put: 在陣列中新增
計算得到索引為  6
reload: 達到閾值,擴容  
計算得到索引為  1
put: 在陣列中新增
計算得到索引為  3
put: 在陣列中新增
計算得到索引為  4
put: 在陣列中新增
計算得到索引為  2
put: 在陣列中新增
計算得到索引為  5
put: 在陣列中新增
計算得到索引為  6
put: 在陣列中新增
put: 在陣列中新增
計算得到索引為  7
put: 在陣列中新增

4.元素在連結串列中間的刪除

這是最特殊的地方,刪除不能簡單的設空,需要考慮鏈會不會斷掉。

程式碼:

package com.lx.myhashmap;

import java.util.HashMap;

public class Demo {
    public static void main(String[] args) {

        MyHashMap map = new MyHashMap(8,0.75);

        map.put("1","hello",-1);
        map.put("3","hello",1);
        map.put("4","hello",1);

        System.out.println(map.get("3",1));
        map.remove("3",1);
        System.out.println(map.get("3",1));
    }
}

輸出:

計算得到索引為  1
put: 在陣列中新增
計算得到索引為  1
put: 進入連結串列
put: 連結串列尾追加
計算得到索引為  1
put: 進入連結串列
put: 連結串列尾追加
hello
remove: 連結串列中已經找到,刪除並連結兩端
null


總結:

主要還是學習思路,目前剩餘連結串列size大於8轉紅黑樹的功能待實現,因為樹的體系比較複雜才學到二叉查詢樹,直接跳過去學紅黑樹不現實,後面等可以寫出紅黑樹了再回頭把這裡完善。