HashMap中 get(K key) 和 put(K key,V value) 的具體過程

說在前面

本文包含手寫泛型HashMap<K,V>爲簡化版,僅爲理解 HashMap 的 get() 和put() 方法的工作過程,非Java源碼。

get(K key) 原理

  1. 先計算出key對應的hash值
    int hash = key.hashCode();
    //此處的 hashCode() 方法爲 Object對象 所屬方法,默認都有
    //自定義的類需要覆寫該方法
  2. 對超出數組範圍的hash值進行處理
    hash = (hash >>> 16)^hash;//java內部自做的優化,爲了使hash值更加均衡,減少衝突
    int index = hash & (table.length - 1);//對下標進行合理化,以免下標越界
    //這樣做可以使index在數組長度範圍內的原因或者一個前提是,這裏的數組的長度一定是2的n次方,
    //這樣table.length - 1 在二進制情況下,除最高位,其餘低位爲一定是1,用hash與這樣的一個數進行與操作
    //即只保留了hash的二進制的低位,就會使hash的範圍一定小於數組長度
  3. 根據正確的hash值(下標值)找到所在的鏈表的頭結點
    Entry<K,V> node = table[index];
  4. 遍歷鏈表,如果key值相等,返回對應的value值,否則返回null
while(node != null){
    if(node.key.equals(key)){
        return node.value;
    }
    node = node.next;
}
  • 具體實現 get(K key)

@Override
    public V get(K key) {
        int hash = key.hashCode();
        hash = (hash >>> 16)^hash;//java內部自做的優化,爲了使hash值更加均衡
        int index = hash & (table.length - 1);
        Entry<K,V> node = table[index];
        while(node != null){
            if(node.key.equals(key)){
                return node.value;
            }
            node = node.next;
        }
        return null;
    }

put(K key,V value) 原理

  1. 先計算出key對應的hash值
  2. 對超出數組範圍的hash值進行處理
  3. 根據正確的hash值(下標值)找到所在的鏈表的頭結點
  4. 如果頭結點==null,直接將新結點賦值給數組的該位置
    Entry<K,V> newNode = new Entry<>(key,value);
    table[index] = newNode;
  5. 否則,遍歷鏈表,找到key相等的節點,並進行value值的替換,返回舊的value值
    Entry<K,V> pre = null;//用來追蹤該段鏈表的最後一個結點,爲尾插做準備,如果採用頭插法,則不需要
    while(node != null){
    if(node.key.equals(key)){
        V oldValue = node.value;
        node.value = value;
        return oldValue;
    }
    pre = node;
    node = node.next;
    }
  6. 如果沒有找到,採用尾插法(1.8)/頭插法(1.7)創建新結點並插入到鏈表中
    pre.next = new Entry<>(key,value);
  7. 將存儲元素數量+1 !!!
  8. 校驗是否需要擴容(需要全部重新計算hash值,因爲數組長度改變了)
    1)擴容原因:爲了減少hash衝突,降低衝突率(插入一個新的key時,會遇到衝突的概率)
    2)負載因子=所有key的數量 / 數組的長度
    3)衝突率和負載因子成正相關!因此爲了降低衝突率,可改變數組的長度!
    4)具體操作:通過計算負載因子並與擴容因子(規定爲0.75)進行比較
    if((double) size / table.length >= 0.75 ){
        resize();
    }
private void resize(){
    /**
    * 1.創造新數組,長度爲原數組的2倍
    * 2.遍歷原數組,找到每一條鏈表的頭節點
    * 3.遍歷每一條鏈表,新建結點並將節點採用頭插法插入到新數組中
    *
    */
    Entry<K,V>[] newTable = new Entry[table.length * 2];
    for(int i = 0; i < table.length; i++){
        Entry<K,V> node = table[i];
        while(node != null){
            Entry<K,V> newNode = new Entry<>(node.key,node.value);
            int hash = node.key.hashCode();
            hash = (hash >>> 16) ^ hash;
            int index = hash ^ (newTable.length - 1);
            //使用頭插,尾插也可以
            newNode.next = newTable[index];
            newTable[index] = newNode;
            node = node.next;
        }
    }
}
  • 具體實現 put(K key,V value)

@Override
public V put(K key, V value) {
    int hash = key.hashCode();
    hash = (hash >>> 16)^hash;//java內部自做的優化語句,爲了使hash值更加均衡
    int index = hash & (table.length - 1);
    Entry<K,V> node = table[index];
    if(node == null){
        Entry<K,V> newNode = new Entry<>(key,value);
        table[index] = newNode;
    }else{
        Entry<K,V> pre = null;
        while(node != null){
            if(node.key.equals(key)){
                V oldValue = node.value;
                node.value = value;
                return oldValue;
            }
            pre = node;
            node = node.next;
        }
        pre.next = new Entry<>(key,value);
    }
    size++;
    if((double) size / table.length >= LOAD_FACTOR_THRESHOLD ){
        resize();
    }
    return null;
}

具體實現HashMap<K,V>

package advance_ds.hashmap;
//接口
public interface Map<K,V> {
    V get(K key);

    V put(K key,V value);

}

/**
 * @author Maria
 * @program JavaDaily
 * @date 2020/3/21 14:51
 */
public class HashMap<K,V> implements Map<K,V> {
    //鏈表的節點類
    private static class Entry<K,V>{
        K key;
        V value;
        Entry<K,V> next;

        public Entry(K key,V value){
            this.key = key;
            this.value = value;
        }
    }

    //基本存儲方式:數組
    private Entry<K,V>[] table = new Entry[16];
    //存儲的元素的個數
    private int size = 0;
    //擴容因子
    private static final double LOAD_FACTOR_THRESHOLD = 0.75;

    @Override
    public V get(K key) {
        /**
         * 1.先計算出key對應的hash值
         * 2.對超出數組範圍的hash值進行處理
         * 3.根據正確的hash值(下標值)找到所在的鏈表的頭結點
         * 4.遍歷鏈表,如果key值相等,返回對應的value值,否則返回null
         */
        int hash = key.hashCode();
        hash = (hash >>> 16)^hash;//java內部自做的優化,爲了使hash值更加均衡
        int index = hash & (table.length - 1);
        Entry<K,V> node = table[index];
        while(node != null){
            if(node.key.equals(key)){
                return node.value;
            }
            node = node.next;
        }
        return null;
    }

    @Override
    public V put(K key, V value) {
        /**
         * 1.先計算出key對應的hash值
         * 2.對超出數組範圍的hash值進行處理
         * 3.根據正確的hash值(下標值)找到所在的鏈表的頭結點
         * 4.如果頭結點==null,直接將新結點賦值給數組的該位置
         * 5.否則,遍歷鏈表,找到key相等的節點,並進行value值的替換,返回舊的value值
         * 6.如果沒有找到,採用尾插法(1.8)/頭插法(1.7)創建新結點並插入到鏈表中
         * 7.將存儲元素數量+1
         * 8.校驗是否需要擴容(需要全部重新計算hash值,因爲數組長度改變了)
         *      擴容原因:爲了減少hash衝突,衝突率:插入一個新的key時,會遇到衝突的概率
         *          負載因子=所有key的數量/數組的長度
         *          衝突率和負載因子成正相關!因此爲了降低衝突率,可改變數組的長度!
         *      具體操作:通過計算負載因子並與擴容因子進行比較
         */

        int hash = key.hashCode();
        hash = (hash >>> 16)^hash;//java內部自做的優化語句,爲了使hash值更加均衡
        int index = hash & (table.length - 1);
        Entry<K,V> node = table[index];
        if(node == null){
            Entry<K,V> newNode = new Entry<>(key,value);
            table[index] = newNode;
        }else{
            Entry<K,V> pre = null;
            while(node != null){
                if(node.key.equals(key)){
                    V oldValue = node.value;
                    node.value = value;
                    return oldValue;
                }
                pre = node;
                node = node.next;
            }
            pre.next = new Entry<>(key,value);
        }
        size++;
        if((double) size / table.length >= LOAD_FACTOR_THRESHOLD ){
            resize();
        }
        return null;

    }

    private void resize(){
        /**
         * 1.創造新數組,長度爲原數組的2倍
         * 2.遍歷原數組,找到每一條鏈表的頭節點
         * 3.遍歷每一條鏈表,新建結點並將節點採用頭插法插入到新數組中
         *
        */
        Entry<K,V>[] newTable = new Entry[table.length * 2];
        for(int i = 0; i < table.length; i++){
            Entry<K,V> node = table[i];
            while(node != null){
                Entry<K,V> newNode = new Entry<>(node.key,node.value);
                int hash = node.key.hashCode();
                hash = (hash >>> 16) ^ hash;
                int index = hash ^ (newTable.length - 1);
                //使用頭插,尾插也可以
                newNode.next = newTable[index];
                newTable[index] = newNode;
                node = node.next;
            }
        }
    }
}

寫一個類來測試一下這段代碼

package advance_ds.hashmap;

import java.util.Objects;

/**
 * @author Maria
 * @program JavaDaily
 * @date 2020/3/21 21:29
 */
public class Person {
    private String name;
    private int age;
    private int gender;

    //自動生成的
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name) &&
                Objects.equals(gender, person.gender);
    }
    //自動生成的
    @Override
    public int hashCode() {
        return Objects.hash(name, age, gender);
    }

    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "p1";
        p1.age = 18;
        p1.gender = 0;
        Person p2 = new Person();
        p2.name = "p1";
        p2.age = 18;
        p2.gender = 0;
        HashMap<Person,Integer> map = new HashMap<>();
        map.put(p1,108);
        System.out.println(map.get(p2));//結果爲108,成功取出!因爲key對應的hash相等
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章