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的理解,如有不當之處,還請指出,我們一起學習。