在上章節中已經介紹了通過紅黑樹實現鍵值對數組的查詢操作,復雜度是logN。有沒有性能更好的算法呢?答案是有。
基本想法就是計算關鍵字的哈希值,再通過哈希值直接獲取對應的鍵值。
這種方法的需要解決的問題是:
如何計算哈希值
如何解決哈系沖突
根據對象中的成員變量的值,按照一定的規則計算出一個整數,這個整數就是哈希值。
哈希值最重要的兩個屬性是:
如果a.equals(b),那麼a.hashCode() == b.hashCode()
理想狀況下,如果!a.equals(b),那麼a.hashCode() != b.hashCode()
Java中的Object對象中已經包含了hashCode函數,由於所有的對象都繼承自Object,因此所有的對象都有hashCode函數。該函數能返回一個整數,代表這個實例的哈希值。
Java中Integer類型的hashCode代碼如下:
public int hashCode() { return value; }
Double類型的hashCode代碼如下:
public int hashCode() { long bits = doubleToLongBits(value); return (int)(bits ^ (bits >>> 32)); }
String類型的hashCode代碼如下:
public int hashCode() { int off = offset; char val[] = value; int len = count; int h = 0; for(int i = 0; i < len; i++) { h = 31*h + val[off++]; } return h; }
這種計算哈系的辦法稱之為Hornor哈希法。這種方法是一種非常簡單的哈系算法,構造哈系沖突是非常容易的。在2011年11月,有人發現Java的HashMap存在漏洞容易讓黑客實現Dos攻擊,它的原理就是構造大量的哈系沖突讓HashMap的復雜度從1變為N,占用大量的CPU資源,BUG的詳細信息戳這裡:https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2012-2739
由於String是不可變的類型,因此可以對hashCode進行緩存。
public class Student { private int number; private String name; private String classname; public int hashCode() { int hash = 17; hash = hash*31 + name.hashCode(); classname = hash*31 + classname.hashCode(); } }
其原理就是按照Hornor哈系法將各個成員變量的哈希值連接在一起。
取模操作就是希望讓哈系值能在0 ~ M-1范圍內,便於通過它來訪問數組。
第一種方法的代碼如下:
private int hash(Key key) { return key.hashCode() % M; }
第二種方法的代碼如下:
private int hash(Key key) { return Math.abs(key.hashCode()) % M; }
第三種方法的代碼如下:
private int hash(Key key) { return (key.hashCode() & 0x7fffffff) % M; }