快速實現一個簡單閹割版的HashMap

2023-02-07 21:00:36

簡單實現一個底層資料結構為陣列 + 連結串列的HashMap,不考慮連結串列長度超過8個時變為紅黑樹的情況。

1.範例圖

2.分析需求

  • put資料時:
    • key值hash後的索引處沒有元素,需要建立連結串列頭節點,放到該位置的陣列空間裡。
    • key值hash後的索引處有元素,說明產生Hash碰撞,需要在連結串列中結尾處掛載節點,如果在遍歷連結串列的過程中,發現了同key的資料,則執行覆蓋即可,不再繼續往下遍歷去掛載新節點。
    • 假設陣列使用的空間超過了總長度的75%,那麼對陣列進行擴容。先建立新陣列,把舊資料寫到新陣列中(此時需要重新根據key計算Hash,因為資料長度變化了,影響計算結果了),在用新資料替換掉原來的舊陣列。
  • get資料時:
    • key值hash後的索引下標處的元素為空的話,則不存在資料。
    • key值hash後的索引下標處存在連結串列的話,需要遍歷連結串列,找到key相對應的value值。

3.程式碼實現

  • Node類實現

    package com.zaevn.hashmap;
    
    /**
     * @author: zae
     * @date: 2023/1/30
     * @time: 11:25
     */
    public class Node {
    
        String key;
        String value;
        Node next;
    
        public Node(String key, String value, Node nextNode) {
            this.key = key;
            this.value = value;
            this.next = nextNode;
        }
    }
    
    
  • LinkNode類實現

    package com.zaevn.hashmap;
    
    /**
     * @author: zae
     * @date: 2023/1/30
     * @time: 11:27
     */
    public class ListNode {
        // 頭節點
        Node head;
    
        /**
         * 新增資料,掛載連結串列的節點
         * @param key
         * @param value
         */
        public void addNode(String key,String value){
            // 如果頭節點是空,則結束
            if(head == null ){return;}
    
            // 如果頭節點不為空,則往下掛載節點
            Node node = new Node(key,value,null);
            Node temp = head;
            while(true){
                // 遇到相同的key,覆蓋資料
                if(key.equals(temp.key)){
                    temp.value = value;
                    return;
                }
    
                if(temp.next == null){
                    break;
                }
                temp = temp.next;
            }
            // 迴圈結束後則掛上資料
            temp.next = node;
        }
    
        /**
         * 獲取資料
         * @param key
         * @return
         */
        public String getNode(String key){
            if(head == null ){return null;}
    
            Node temp = head;
            while(true){
                if(key.equals(temp.key)){
                    return temp.value;
                }
                if(temp.next == null){
                    break;
                }
                temp = temp.next;
            }
            return null;
        }
    }
    
    
  • MyHashMap類實現

    package com.zaevn.hashmap;
    
    /**
     * @author: zae
     * @date: 2023/1/30
     * @time: 11:27
     */
    public class MyHashMap {
        // 陣列初始化:2的n次方
        ListNode[] map = new ListNode[8];
        // ListNode的個數
        int size;
    
        // 由於擴容時是先建立一個新陣列,因此先宣告出來
        ListNode[] mapNew;
        int sizeNew;
    
        /**
         * put方法
         * @param key
         * @param value
         */
        public void put(String key,String value){
            if(size>map.length * 0.75){
                System.out.println("開始進行擴容,當前size="+size+",陣列長度為:"+map.length);
                doExtendMap();
                System.out.println("擴容結束,當前size="+size+",陣列長度為:"+map.length);
            }
    
            // 1.對key進行hash演演算法然後取模
            int index = Math.abs(key.hashCode())%map.length;
    
            ListNode listNode = map[index];
            // 如果索引位置的元素為空,則新加一個元素(建立頭節點)
            if(listNode == null){
                ListNode listNodeNew = new ListNode();
                Node node = new Node(key,value,null);
                listNodeNew.head = node;
                map[index] = listNodeNew;
                size ++;
            }else{
                // 如果索引位置的元素不為空,則往連結串列中掛載資料
               listNode.addNode(key,value);
            }
        }
    
        public String get(String key){
            // 1.對key進行hash演演算法然後取模
            int index = Math.abs(key.hashCode())%map.length;
    
            if(map[index] == null){
                return null;
            }else{
                return map[index].getNode(key);
            }
        }
    
        /**
         * 達到閾值後開始進行擴容
         */
        public void doExtendMap(){
            sizeNew = 0;
            // 1.先建立一個新的陣列,長度為原來的二倍
            mapNew = new ListNode[map.length * 2];
    
            // 2.將舊資料對映到新的陣列上(因為陣列長度變化,因此hash規則變化,所有的值需要重新計算hash值)
            for(int i = 0;i<map.length;i++){
                ListNode listNode = map[i];
                if(listNode == null){
                    continue;
                }
                Node temp = listNode.head;
                while (true){
                    doPutData(mapNew,temp.key,temp.value);
                    if(temp.next == null){
                        break;
                    }
                    temp = temp.next;
                }
            }
    
            // 3.將新的陣列替換舊的陣列
            map = mapNew;
            this.size = sizeNew;
        }
    
        private void doPutData(ListNode[] mapParam,String key,String value){
            int index = Math.abs(key.hashCode())%mapParam.length;
            ListNode listNode = mapParam[index];
            if(listNode == null){
                ListNode listNodeNew = new ListNode();
                Node node = new Node(key,value,null);
                listNodeNew.head = node;
                mapParam[index] = listNodeNew;
                sizeNew ++;
            }else{
                listNode.addNode(key,value);
            }
        }
    
        public static void main(String[] args) {
            // 1、一般校驗
            MyHashMap hashMap0=new MyHashMap();
            hashMap0.put("key1","value1");
            System.out.println("一般校驗:"+hashMap0.get("key1"));
            System.out.println("--------------------------------------------");
    
    
            // 2、同key覆蓋校驗
            MyHashMap hashMap1=new MyHashMap();
            hashMap1.put("key2","value00");
            hashMap1.put("key2","value01");
            System.out.println("同key覆蓋校驗:"+hashMap1.get("key2"));
            System.out.println("--------------------------------------------");
    
            // 3、雜湊碰撞校驗(k1和k9的經過雜湊計算後得到的索引都是6)
            MyHashMap hashMap2=new MyHashMap();
            hashMap2.put("k1","value_k1");
            hashMap2.put("k9","value_k9");
            System.out.println("雜湊碰撞校驗:k1:"+hashMap2.get("k1")+"  k9:"+hashMap2.get("k9"));
            System.out.println("--------------------------------------------");
    
    
            // 4、擴容校驗
            MyHashMap hashMap3=new MyHashMap();
            hashMap3.put("m3","cccccc");
            hashMap3.put("c1","kkkkkk");
            hashMap3.put("c2","mmmmmmm");
            hashMap3.put("b1","bbbbbbb");
            hashMap3.put("m1","cccccc");
            hashMap3.put("c3","kkkkkk");
            hashMap3.put("c4","mmmmmmm");
            hashMap3.put("b2","bbbbbbb");
            hashMap3.put("m2","cccccc");
            hashMap3.put("c5","kkkkkk");
            hashMap3.put("c6","mmmmmmm");
            hashMap3.put("b3","bbbbbbb");
            System.out.println("擴容後的c4:"+hashMap3.get("c4"));
            System.out.println("擴容後的b3:"+hashMap3.get("b3"));
        }
    
    }
    
    

3.執行結果