自己動手用java寫一個hashMap


入坑java很多年了,現在總結一下自己學到的東西。

1、首先我們先來聊聊什么是HashMap?
  什么是hash?hash用中文的說法就叫做“散列”,通俗的講就是把任意長度的字符串輸入,經過hash計算出固定長度的字符串。而這個字符串就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,而不可能從散列值來唯一的確定輸入值。
2、hash碰撞?
  上面已經說到了,不同的輸入值可能會計算出相同的輸出,這就是碰撞。
3、java中的hashMap是怎么實現的?
  java的hashMap底層是用數組加鏈表實現的。存儲對象的時候,我們是將K/V傳給put方法,然后這個put方法會調用hashCode方法計算出hash值(也就是數組的下標)從而得到存儲的位置,進一步存儲。那么問題來了,我們都知道,創建數組的時候是要指定數組的長度的,萬一它的初始化的數組不夠用怎么辦?其實hashMap早都給你想好了,hashMap會根據當前你存儲的數據的占用情況自動調整數組的長度。獲取對象的時候,我們是把K傳給get()方法,它就會調用hashCode計算hash的值從而得到存儲位置,並且進一步調用equals方法確定鍵值對。發生碰撞的時候:HashMap通過鏈表將產生碰撞沖突的元素組織起來,在Java8中,如果一個bucket中碰撞沖突的元素超過某個限制(默認是8),則使用紅黑樹來替換鏈表,從而提高速度。關於解決hash沖突的方法在這里暫且不說,我直接上代碼了。
4、代碼
  
首先我們先定義一個Map接口:

package com.jian.utils;
/**
 * Map接口
 * @author weijianyi
 *
 * @param <K>
 * @param <V>
 */
public interface Map<K,V> {
     
    //向Map中插入值
    public V put(K k,V v); 
    
    //根據key獲取hashMap中的值
    public V get(K k);
    
    //獲得Map中元素的個數
    public int size();
    
    //獲取Map中,鍵值對的對象
    interface Entry<K,V>{
        K getKey();
        
        V getValue();
        
        V setValue(V v);
        
    }
}

第二步定義我們的MyhashMap類:

package com.jian.utils;
/**
 * hashMap的實現 數組+鏈表
 * 
 * @author weijianyi
 *
 * @param <K>
 * @param <V>
 */
public class HashMap<K, V> implements Map<K, V> {
    // 數據存儲的結構==>數組+鏈表
    Node<K, V>[] array = null;

    // 數組/哈希桶的長度,就是數組初始化的長度
    private static int defaultLength = 16;

    /**加載因子/擴容因子,就是給數組界定一個存儲閾值,
             當數組沾滿整個數組的75%的時候,觸發擴容操作)**/
    private static double factor = 0.75D;

    // hashMap中的元素個數
    private int size;

    // put元素方法
    @SuppressWarnings("unchecked")
    @Override
    public V put(K k, V v) {

        // 1.載機制,使用的時候進行分配
        if (array == null) {
            //初始化一個數組,給的長度是defaultLength
            array = new Node[defaultLength];
        }

        // 2.通過hash算法,計算出具體插入的位置,也就是數組的下標
        int index = position(k, defaultLength);

        // 擴容,判斷是否需要擴容
        // 擴容規則,元素的個數size 大於 桶的尺寸*加載因子
        if (size > defaultLength * factor) {
            resize();
        }

        // 3.放入要插入的元素(添加到鏈表)
        Node<K, V> node = array[index];
        //先判斷一下這個鏈表的index位置是否為空
        if (node == null) {
            //鏈表的index這個位置是空的,直接新建該鏈表並將值放入該位置
            array[index] = new Node<K, V>(k, v, null);
            //元素的個數加自增1
            size++;
        } else {
            if (k.equals(node.getKey()) || k == node.getKey()) {
                return node.setValue(v);
            } else {
                array[index] = new Node<K, V>(k, v, node);
                size++;
            }
        }

        return null;
    }

    // 擴容,並且重新排列元素
    private void resize() {
        // 翻倍擴容
        // 1.創建新的array臨時變量,相當於defaultlength*2
        @SuppressWarnings("unchecked")
        Node<K, V>[] temp = new Node[defaultLength << 1];

        // 2.重新計算散列值,插入到新的array中去。 code=key % defaultLength ==> code=key %
        // defaultLength*2
        Node<K, V> node = null;
        for (int i = 0; i < array.length; i++) {
            node = array[i];
            while (node != null) {
                // 重新散列
                int index = position(node.getKey(), temp.length);
                // 插入鏈表的頭部
                Node<K, V> next = node.next;
                // 3
                node.next = temp[index];
                // 1
                temp[index] = node;
                // 2
                node = next;

            }
        }

        // 3.替換掉舊的array
        array = temp;
        //更新默認的擴容因子的值
        defaultLength = temp.length;
        temp = null;

    }

    // 計算位置
    private int position(K k, int length) {
        int code = k.hashCode();

        // 取模算法
        return code % (length - 1);

        // 求與算法
        // return code & (defaultLength-1);
    }
    /**
               * 用K獲取hashMap 的K對應的值
     */
    @Override
    public V get(K k) {
        if (array != null) {
            int index = position(k, defaultLength);
            Node<K, V> node = array[index];
            // 遍歷鏈表
            while (node != null) {
                // 如果key值相同返回value
                if (node.getKey() == k) {
                    return node.getValue();
                } else {
                    // 如果key值不同則調到下一個元素
                    node = node.next;
                }
            }
        }

        return null;
    }
    /**
                * 獲取hashMap元素個數
     */
    @Override
    public int size() {

        return size;
    }

    // 鏈表節點(鏈表類)
    static class Node<K, V> implements Entry<K, V> {
        K key;
        V value;
        //表示下一個節點
        Node<K, V> next;

        // 構造一個包含當前節點和下一個節點的鏈表
        public Node(K key, V value, Node<K, V> next) {
            super();
            this.key = key;
            this.value = value;
            this.next = next;
        }
        /**
                           * 鏈表的get和set方法
         */
        @Override
        public K getKey() {
            return this.key;
        }

        @Override
        public V getValue() {
            return this.value;
        }

        @Override
        public V setValue(V v) {
            V oldValue = this.value;
            this.value = v;
            return oldValue;
        }

    }

    // 測試方法
    public void print() {
        System.out.println("===============================");
        if (array != null) {
            Node<K, V> node = null;
            for (int i = 0; i < array.length; i++) {
                node = array[i];
                System.out.print("下標[" + i + "]");
                // 遍歷鏈表
                while (node != null) {
                    System.out.print("[" + node.getKey() + ":" + node.getValue() + "]");
                    if (node.next != null) {
                        node = node.next;
                    } else {
                        // 到尾部元素
                        node = null;
                    }
                }
                System.out.println();
            }

        }
    }
}

到此,自己動手寫一個hashMap結束,其實並不難,只要大家認真的去看他每一個步驟和方法流程,我相信大家都能自己寫出一個。我這里只是寫了一個簡易的,如果有什么地方不對或者有什么修改意見,歡迎大家評論探討,我們一起進步。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM