1. 程式人生 > >Java HashSet與hashCode詳解

Java HashSet與hashCode詳解

在進入主題之前,先來扯一些前話,幫大家複習一下基礎,看下面的一個例子,比如我們先定義一個Point類

public class Point {

    public int x;
    public int y;
    public Point(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }   
}

如果我們不重寫它的equals方法,那麼Point p1 = new Point(3,3); Point p2 = new Point(3,3); System.out.println(p1 == p2);返回的是false,因為我們比較它們是否相等是呼叫該類的equals方法,如果沒有重寫該方法,預設equals方法是比較他們的hashCode值,通常是根據記憶體地址換算過來的。當重寫equals方法,如下:

public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Point other = (Point) obj;
        if (x != other.x)
            return false;
        if (y != other.y)
            return false;
        return true;
    }

那麼p1與p2便是相等的。我們再來看下面一個例子:

ArrayList<Point> list = new ArrayList<Point>();

Point p1 = new Point(3,3);

Point p2 = new Point(3,3)

list.add(p1);

list.remove(p2);

System.out.println(list.size());

我們知道當呼叫ArrayList的remove方法時,它會把集合中的每個元素依次取出來與p2相比較(呼叫該類的equals方法),如果相等則刪除。所以如果不重寫equals方法,p1不等於p2,上面輸出1,重寫之後便輸出0。

以上集合中只存入2個元素,如果存入一萬個元素,並且沒有包含要查詢的元素,則意味著需要比較一萬次。

為了提高查詢元素的效率,便有了雜湊演算法。當我們存入一個元素時,它會根據雜湊演算法計算出該元素的雜湊值,每個雜湊值對應著一片儲存區域。如下圖:


簡單的說我們以前要查詢一個元素,需要把集合中每個元素都取出來比較,而現在在查詢之前,先計算出該元素的雜湊值,直接去對應雜湊值所指向的區域去查詢元素,這樣便不用一個個比較,提高了查詢效率。

HashSet就是採用雜湊演算法存取物件的集合,它內部採用對某個數字n進行取餘的方式對雜湊碼進行分組和劃分物件的儲存區域,Object類中定義了一個hashCode()方法來返回每個Java物件的雜湊碼,當從HashSet集合中查詢某個物件時,Java系統首先呼叫物件的hashCode()方法獲得該物件的雜湊碼,然後根據雜湊碼找到對應的儲存區域,最後取出該儲存區域的每個元素與該物件進行equals()方法比較,這樣不用遍歷集合中所有元素就可以得到結論。

為了保證一個物件能在HashSet正常儲存,要求這個類的兩個例項物件用equals方法比較結果相等的同時他們的雜湊碼也必須相等,即當obj1.equals(obj2)為true時,那麼obj1.hashCode()==obj2.hashCode()也為true(兩個物件如果不重寫hashCode方法,預設返回值是不同的,因為它的返回值是通過物件的記憶體地址推算出來的)。比如下面的例子:

public void hashsetTest(){
        HashSet<Point> hPoints = new HashSet<Point>();
        Point p1 = new Point(3, 3);
        Point p2 = new Point(3, 3);
        hPoints.add(p1);
        hPoints.add(p2);
        System.out.println(hPoints.size());

}

當沒有重寫hashCode()方法時,新增p1時計算出一個雜湊值,放入對應的儲存區域,當加入p2時,由於預設hashCode()方法返回值不同,p2就被存放另一個區域,所以輸出結果為2。當重寫之後:

public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + x;
        result = prime * result + y;
        return result;
    }

存入p2時,它計算出的雜湊值與p1相同,就存放到p1那個區域,結果已經有一個相同的元素,所以存不進去,輸出結果為1。

另外我們還需注意一點,當一個物件被儲存進HashSet集合中以後,就不要修改這個物件中參與計算雜湊值的欄位了。如下例子:

public void hashsetTest(){
        HashSet<Point> hPoints = new HashSet<Point>();
        Point p1 = new Point(3, 3);
        Point p2 = new Point(4, 4);
        hPoints.add(p1);
        hPoints.add(p2);
        p1.x = 5;
        hPoints.remove(p1);
        System.out.println(hPoints.size());

}

按理說在集合中新增兩個元素又刪除了其中一個元素,此時應該輸出結果為1,但實際上結果為2。這時因為在刪除p1之前,修改了p1的y值,刪除時去計算該元素的雜湊值與存入時的不同,指向了另外一個區域,然後沒找到相同的元素,就沒有刪除成功。這樣就會造成記憶體洩漏,本以為已經刪除的無用元素卻仍然留在記憶體中。

講到這裡我相信大家都明白的hashCode的作用了。總結一下:

1.當一個物件以hashSet存取的時候,才需要重寫它的hashCode方法,如果是ArrayList就沒必要了,當然equals方法是都要重寫的。

2.hashCode()與equals方法都用來比較兩個物件是否相等,只不過HashSet為了提高比較效率,當物件存取時先比較hsahCode的值,如果不同,直接pass,如果相同,再去hashCode的值對應的區域查詢元素,再用equals方法進行進一步比較。

好啦,以上就是我對hashCode的理解,如有不當之處,還請指出,我們一起學習。