錯誤的hashcode/equals方法會出現什麼問題?

1、HashCode簡介

首先,咱們看下載頂級父類Object如何解釋HashCode()方法的:java

image.png

我把解釋單獨拿出來:數組

image.png

總結爲:markdown

  • hasdcode值具備必定的穩定性,屢次調用返回結果要保持一致,而且是整數
  • equals方法執行結果和hashcode值的結果一致性要保持一致
  • 不是必須的操做,可是重寫能夠提升hash表存儲的性能(減小碰撞)

2、hashcode的做用

  • 假設自定義一個User對象:
package dream.on.sakura.entity;
import lombok.Data;
import lombok.ToString;
import java.util.Objects;
/** * @ClassName User * @function [業務功能] * @Author lcz * @Date 2021/07/12 11:06 */
@Data@ToString public class User {
	private String name;
	private String phone;
	private int age;
}
複製代碼

hashcode/equals方法都不進行重寫;數據結構

TestMain代碼:ide

import dream.on.sakura.entity.User;

import java.util.HashMap;

/** * @ClassName TestMain * @function [測試程序入口] * @Author lcz * @Date 2021/07/12 11:06 */
public class TestMain {
    public static void main(String[] args) {
        User userA = new User();
        userA.setName("ABCDEa123abc");
        System.out.println(userA.hashCode());

        User userB = new User();
        userB.setName("ABCDFB123abc");
        System.out.println(userB.hashCode());

        System.out.println(userA.equals(userB));

        HashMap<User, User> container = new HashMap<>();
        //隨便放幾個
        User userC = new User();
        userC.setName("c");
        User userD = new User();
        userD.setName("d");
        container.put(userC, userC);
        container.put(userD, userD);

        container.put(userA, userA);
        container.put(userB, userB);
        System.out.println(container.size());
        System.out.println(container.get(userA));
    }
}
複製代碼

代碼中我精心設計了一種狀況,hashcode值相同可是equals方法不相同:性能

image.png

這時候咱們hashMap容器中才塞入了四個數據對象,然而裏面的結構已是:測試

  1. 斷點截圖table中的數據結構

image.png

image.png

  1. 圖標更直觀展現:

image.png

上面是在debugger狀態下查看了一下hashMap中的數據的存儲狀態,能夠看出其中數據存儲的緊湊程序並不理想,甚至是在四個數據狀況下出現了堆壓。優化

3、重寫HashCode方法+錯誤的equals方法

重寫hashCode方法,加上錯誤的equals方法或者不重寫可能會致使數據覆蓋;這裏我爲了模擬數據覆蓋的狀況,就重寫了一個錯誤的equals方法:this

package dream.on.sakura.entity;

import lombok.Data;
import lombok.ToString;

import java.util.Objects;

/** * @ClassName User * @function [業務功能] * @Author lcz * @Date 2021/07/12 11:06 */
@Data
@ToString
public class User {
    private String name;
    private String phone;
    private int age;

    /*@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return age == user.age && Objects.equals(name, user.name) && Objects.equals(phone, user.phone); }*/

    @Override
    public int hashCode() {
        return Objects.hash(name, phone, age);
    }

    /** * 錯誤的hashcode方法 * @param o * @return */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return hashCode() == o.hashCode();
    }
}
複製代碼

TestMain代碼中的內容不變:spa

一、debugger看下代碼在執行userB對象放入以前的容器狀態:

image.png

二、userB放入以後容器的狀態:

image.png

能夠看出這時候container對象的size明顯是不對的,這時候table中存儲的內容爲:

image.png

總結:key仍是以前的哪一個key,可是value早就不見了

這是由於hashmap在執行put操做的時候,hashcode值定位定位數組位置;其次,在執行equals方法判斷內容是否相等;

能夠把上面的覆蓋過程理解爲發生的hash碰撞後在經過equals方法判斷裏面的值是否是同樣的,不是追加鏈表後面,是就覆蓋並彈出原來的舊值。

寫到這裏:是否是可讓你從底層存儲的具體狀況更加明白理解爲何引用數據類型要重寫兩個方法了?

4、那具體怎麼重寫呢

咱們能夠參考下jdk源碼的操做方法:

image.png

在名著 《Effective Java》第 42 頁就有對 hashCode 爲何採用 31 作了說明:

之因此使用 31, 是由於他是一個奇素數。若是乘數是偶數,而且乘法溢出的話,信息就會丟失,由於與2相乘等價於移位運算(低位補0)。使用素數的好處並不很明顯,可是習慣上使用素數來計算散列結果。 31 有個很好的性能,即用移位和減法來代替乘法,能夠獲得更好的性能: 31 * i == (i << 5)- i, 現代的 VM 能夠自動完成這種優化。這個公式能夠很簡單的推導出來。

還有一個就是上面這句話是我抄人家的!!!

相關文章
相關標籤/搜索