天天看點

學習Java哈希表HashMap時可能會遇到的小困惑

而當你使用HashMap存儲鍵值對時,有可能會遇到一個疑問。

下面舉一個我遇到的問題:

Person是一個實體類(已經重寫了equals和hashCode方法) ,這裡的HashMap是官方的HashMap

= new com.mj.set.Person(12,1.67f,"jack");
    Person p2 = new com.mj.set.Person(12,1.67f,"jack");
    java.util.Map<Object, Integer> map2 = new java.util.HashMap<>();
    map2.put(p1,1);
    map2.put(p2,2);
    System.out.println(map2.size());
    System.out.println(map2.containsKey(p1));      

當你在主函數執行這段代碼時,會發現結果為1,true。

而你可能會有這樣的疑惑:你不是已經用p2覆寫了p1嗎,為什麼map2中還是包含p1?

作出解釋:

你翻閱Java官方的HashMap你會發現,那個containsKey(K key)方法是通過傳入hash(key);,以及key查找node是否為空,如果為空,則傳回false, 否則傳回true。

getNode方法

/**
     * Implements Map.get and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }      
/**
     * Returns {@code true} if this map contains a mapping for the
     * specified key.
     *
     * @param   key   The key whose presence in this map is to be tested
     * @return {@code true} if this map contains a mapping for the specified
     * key.
     */
    public boolean containsKey(Object key) {
        return getNode(hash(key), key) != null;
    }      

而你細看上面建立的兩個對象你會發現,兩個對象的成員變量的内容都是一緻的

Person類

package com.mj.set;

public class Person implements Comparable<Person> {
  private int age;   // 10  20
  private float height; // 1.55 1.67
  private String name; // "jack" "rose"
  
  public Person(int age, float height, String name) {
    this.age = age;
    this.height = height;
    this.name = name;
  }
  
  @Override
  /**
   * 用來比較2個對象是否相等
   */
  public boolean equals(Object obj) {
    // 記憶體位址
    if (this == obj) return true;
    if (obj == null || obj.getClass() != getClass()) return false;
    // if (obj == null || !(obj instanceof Person)) return false;
    
    // 比較成員變量
    Person person = (Person) obj;
    return person.age == age
        && person.height == height
        && (person.name == null ? name == null : person.name.equals(name));
  }
  
  @Override
  public int hashCode() {
    int hashCode = Integer.hashCode(age);
    hashCode = hashCode * 31 + Float.hashCode(height);
    hashCode = hashCode * 31 + (name != null ? name.hashCode() : 0);
    return hashCode;
  }

  @Override
  public int compareTo(Person o) {
    return age - o.age;
  }
}      

既然内容都一緻,這就意味着hashCode的傳回值都一緻,則hash函數的結果也一緻:

Java官方的hash函數

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }      

而當你檢視官方的getNode方法時,你會發現有這麼幾句代碼

學習Java哈希表HashMap時可能會遇到的小困惑

即當hash()的傳回值相同以及key.equals(k)都為true時,才傳回找到的對應的節點,而之前已經說過p1與p2的hash值是一定相等的,

再看equals方法

/**
   * 用來比較2個對象是否相等
   */
  public boolean equals(Object obj) {
    // 記憶體位址
    if (this == obj) return true;
    if (obj == null || obj.getClass() != getClass()) return false;
    // if (obj == null || !(obj instanceof Person)) return false;
    
    // 比較成員變量
    Person person = (Person) obj;
    return person.age == age
        && person.height == height
        && (person.name == null ? name == null : person.name.equals(name));
  }      

由于p1和p2成員變量的内容一緻,則euqals方法一定傳回true