目錄介紹
- 1.Hash的做用介紹
- 1.1 Hash的定義
- 1.2 Hash函數特性
- 1.3 Hash的使用場景
- 2.如何判斷兩個對象相等
- 2.1 判斷兩個字符串
- 2.2 判斷兩個int數值
- 2.3 其餘基本類型
- 3.HashCode深刻分析
- 3.0 HashCode是什麼
- 3.1 爲何要重寫HashCode
- 3.2 HashCode源代碼分析
- 3.3 HashCode帶來的疑問
- 3.4 HashCode的做用
- 3.5 HashMap中的HashCode
- 3.6 可直接用hashcode判斷兩個對象是否相等
- 4.Hash表是什麼
- 4.1 Hash表定義
- 4.2 Hash表簡單介紹
- 5.Hash中的算法應用
- 5.1 基礎算法
- 5.2 經典算法[摘自網絡]
- 5.3 Hash碰撞[摘自網絡]
- 6.Hash在Java中的應用場景
- 6.1 equals與hashCode有兩個注意點
- 6.2 以HashSet爲例說明hashCode()的做用
- 6.3 以HashMap爲例說明Hash的做用
- 6.4
- 7.版本更新狀況
- 8.其餘介紹
1.Hash的做用介紹
1.1 Hash的定義
- 散列(哈希)函數
- 把任意長度的輸入(又叫作預映射pre-image)經過散列算法變換成固定長度的輸出,該輸出就是散列值,是一種壓縮映射。
- 或者說一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。
1.2 Hash函數特性
- h(k1)≠h(k2)則k1≠k2,即散列值不相同,則輸入值即預映射不一樣
- 若是k1≠k2,h(k1)=h(k2) 則發生碰撞;
- 若是h(k1)=h(k2),k1不必定等於k2;
1.3 Hash的使用場景
- 好比說咱們下載一個文件,文件的下載過程當中會通過不少網絡服務器、路由器的中轉,如何保證這個文件就是咱們所須要的呢?咱們不可能去一一檢測這個文件的每一個字節,也不能簡單地利用文件名、文件大小這些極容易假裝的信息,這時候,就須要一種指紋同樣的標誌來檢查文件的可靠性,這種指紋就是咱們如今所用的Hash算法(也叫散列算法)。
- 散列算法就是一種以較短的信息來保證文件惟一性的標誌,這種標誌與文件的每個字節都相關,並且難以找到逆向規律。所以,當原有文件發生改變時,其標誌值也會發生改變,從而告訴文件使用者當前的文件已經不是你所需求的文件。
- 這種標誌有何意義呢?以前文件下載過程就是一個很好的例子,事實上,如今大部分的網絡部署和版本控制工具都在使用散列算法來保證文件可靠性。
2.如何判斷兩個對象相等
2.1 判斷兩個字符串
String a = "yc1";
String b = "yc2";
boolean isEqual = a.equals(b);
- 固然Object的子類能夠經過重寫equals的方法,實現子類自身的對象是否相等的邏輯;String是Object的子類,查看下它的equals方法
//在Object類中
public boolean equals(Object obj) {
//直接比較的是地址
return (this == obj);
}
//在String類中
public boolean equals(Object anObject) {
//直接比較的是地址
if (this == anObject) {
return true;
}
//盤旋是不是字符串String類型
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = count;
if (n == anotherString.count) {
int i = 0;
//循環判斷每一個字符是否相等
while (n-- != 0) {
if (charAt(i) != anotherString.charAt(i))
return false;
i++;
}
return true;
}
}
return false;
}
2.2 判斷兩個int數值
- Integer類的equals方法又是如何實現的呢?
Integer a = Integer.valueOf("1");
Integer b = Integer.valueOf("2");
boolean ab = a.equals(b);
public boolean equals(Object obj) {
//先判斷是不是Integer類型
if (obj instanceof Integer) {
//轉爲int值後進行比較
return value == ((Integer)obj).intValue();
}
return false;
}
2.3 其餘基本類型
//short類型
@Override
public int hashCode() {
return Short.hashCode(value);
}
public static int hashCode(short value) {
return (int)value;
}
//Byte類型
@Override
public int hashCode() {
return Byte.hashCode(value);
}
public static int hashCode(byte value) {
return (int)value;
}
//Long類型
@Override
public int hashCode() {
return Long.hashCode(value);
}
public static int hashCode(long value) {
return (int)(value ^ (value >>> 32));
}
//long類型做爲索引範圍太大,須要轉爲int類型。這裏簡單的獲取低32位容易致使散列不均,由於高位部分沒有被利用。因此這裏採用邏輯右移32位,讓高32位和低32位進行XOR操做,致使高位低位都能被利用到
//Boolean類型
@Override
public int hashCode() {
return Boolean.hashCode(value);
}
public static int hashCode(boolean value) {
return value ? 1231 : 1237;
}
//採用兩個質數做爲true或false的索引。這兩個質數足夠大,用來做爲索引時,出現碰撞的可能性低。
3.HashCode深刻分析
3.0 HashCode是什麼
- HashCode是Object的一個方法,hashCode方法返回一個hash code值,且這個方法是爲了更好的支持hash表,好比String,Set,HashTable、HashMap等;
3.1 爲何要重寫HashCode
- 若是用 equal 去比較的話,若是存在1000個元素,你 new 一個新的元素出來,須要去調用1000次equal去逐個和他們比較是不是同一個對象,這樣會大大下降效率。hashcode其實是返回對象的存儲地址,若是這個位置上沒有元素,就把元素直接存儲在上面,若是這個位置上已經存在元素,這個時候纔去調用equal方法與新元素進行比較,相同的話就不存了,散列到其餘地址上
3.2 HashCode源代碼分析
public int hashCode() {
int lockWord = shadow$_monitor_;
final int lockWordStateMask = 0xC0000000; // Top 2 bits.
final int lockWordStateHash = 0x80000000; // Top 2 bits are value 2 (kStateHash).
final int lockWordHashMask = 0x0FFFFFFF; // Low 28 bits.
if ((lockWord & lockWordStateMask) == lockWordStateHash) {
return lockWord & lockWordHashMask;
}
//返回的是對象引用地址
return System.identityHashCode(this);
}
public int hashCode() {
int h = hash;
if (h == 0 && count > 0) {
for (int i = 0; i < count; i++) {
h = 31 * h + charAt(i);
}
hash = h;
}
return h;
}
public int hashCode() {
//int值
return value;
}
3.3 HashCode帶來的疑問
- 爲什麼重寫equals建議同時重寫hashCode?
- hashCode是什麼?
- hashCode做用?
- hash code(hash值)是什麼?
- hash table(hash表)是什麼?
- hashCode方法對hash表有益處?
- hashCode方法對不是hash有益處嗎?
3.4 HashCode的做用
- 減小查找次數,提升程序效率
- 例如查找是否存在重複值
- h(k1)≠h(k2)則k1≠k2
- 首先查看h(k2)輸出值(內存地址),查看該內存地址是否存在值;
- 若是無,則表示該值不存在重複值;
- 若是有,則進行值比較,相同則表示該值已經存在散列列表中,若是不相同則再進行一個一個值比較;而無需一開始就一個一個值的比較,減小了查找次數
3.5 HashMap中的HashCode
- 在Java中也同樣,hashCode方法的主要做用是爲了配合基於散列的集合一塊兒正常運行,這樣的散列集合包括HashSet、HashMap以及HashTable。
- 爲何這麼說呢?考慮一種狀況,當向集合中插入對象時,如何判別在集合中是否已經存在該對象了?(注意:集合中不容許重複的元素存在)
- 也許大多數人都會想到調用equals方法來逐個進行比較,這個方法確實可行。可是若是集合中已經存在一萬條數據或者更多的數據,若是採用equals方法去逐一比較,效率必然是一個問題。
- 此時hashCode方法的做用就體現出來了,當集合要添加新的對象時,先調用這個對象的hashCode方法,獲得對應的hashcode值,實際上在HashMap的具體實現中會用一個table保存已經存進去的對象的hashcode值,若是table中沒有該hashcode值,它就能夠直接存進去,不用再進行任何比較了;若是存在該hashcode值, 就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的地址,因此這裏存在一個衝突解決的問題,這樣一來實際調用equals方法的次數就大大下降了,說通俗一點:Java中的hashCode方法就是根據必定的規則將與對象相關的信息(好比對象的存儲地址,對象的字段等)映射成一個數值,這個數值稱做爲散列值。下面這段代碼是java.util.HashMap的中put方法的具體實現:
- put方法是用來向HashMap中添加新的元素,從put方法的具體實現可知,會先調用hashCode方法獲得該元素的hashCode值,而後查看table中是否存在該hashCode值,若是存在則調用equals方法從新肯定是否存在該元素,若是存在,則更新value值,不然將新的元素添加到HashMap中。從這裏能夠看出,hashCode方法的存在是爲了減小equals方法的調用次數,從而提升程序效率。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
3.6 可直接用hashcode判斷兩個對象是否相等
- 確定是不能夠的,由於不一樣的對象可能會生成相同的hashcode值。雖然不能根據hashcode值判斷兩個對象是否相等,可是能夠直接根據hashcode值判斷兩個對象不等,若是兩個對象的hashcode值不等,則一定是兩個不一樣的對象。若是要判斷兩個對象是否真正相等,必須經過equals方法。
- 也就是說對於兩個對象,若是調用equals方法獲得的結果爲true,則兩個對象的hashcode值一定相等;
- 若是equals方法獲得的結果爲false,則兩個對象的hashcode值不必定不一樣;
- 若是兩個對象的hashcode值不等,則equals方法獲得的結果一定爲false;
- 若是兩個對象的hashcode值相等,則equals方法獲得的結果未知。
4.Hash表是什麼
4.1 Hash表定義
- 根據關鍵碼值(KEY-VALUE)而直接進行訪問的數據結構;它經過把關鍵碼值(KEY-VALUE)映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫作散列函數,存放記錄的數組叫作散列表。
4.2 Hash表簡單介紹
- 將k做爲輸入值,h(k)輸出值做爲內存地址,該內存地址用來存放value,而後能夠經過k獲取到value存放的地址,從而獲取value信息。
5.Hash中的算法應用
5.1 基礎算法
- 好比,Java中的String.hashCode使用乘法和加法
public int hashCode() {
int h = hash;
if (h == 0 && count > 0) {
for (int i = 0; i < count; i++) {
//乘法與加法
h = 31 * h + charAt(i);
}
hash = h;
}
return h;
}
5.2 經典算法[摘自網絡]
5.3 Hash碰撞[摘自網絡]
- hash是存在碰撞的,若是k1≠k2,h(k1)=h(k2) 則發生碰撞;
6.Hash在Java中的應用場景
6.1 equals與hashCode有兩個注意點
- equals相同,則hashCode相同;而hashCode相同,equals不必定相同
- 若是equals相同,hashCode不相同,有可能會形成上述重複值等狀況,這種狀況是不容許的;
- 而hasCode相同,可是equals不必定相同,有多是由於發生了碰撞而碰撞是有可能性發生的
6.2 以HashSet爲例說明hashCode()的做用
- 假設,HashSet中已經有1000個元素。當插入第1001個元素時,須要怎麼處理?
- 由於HashSet是Set集合,它容許有重複元素。「將第1001個元素逐個的和前面1000個元素進行比較」?
- 顯然,這個效率是相等低下的。散列表很好的解決了這個問題,它根據元素的散列碼計算出元素在散列表中的位置,而後將元素插入該位置便可。對於相同的元素,天然是隻保存了一個。
- 由此可知,若兩個元素相等,它們的散列碼必定相等;但反過來確不必定。在散列表中,
- 一、若是兩個對象相等,那麼它們的hashCode()值必定要相同;
- 二、若是兩個對象hashCode()相等,它們並不必定相等。
- 注意:這是在散列表中的狀況。在非散列表中必定如此!
6.3 以HashMap爲例說明Hash的做用
//put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//remove方法
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
//get方法
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
//上面這幾個方法都用到了這個方法
static final int hash(Object key) {
int h;
//計算hashCode,並沒有符號移動到低位
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
舉個例子: h = 363771819^(363771819 >>> 16)
0001 0101 1010 1110 1011 0111 1010 1011(363771819)
0000 0000 0000 0000 0001 0101 1010 1110(5550) XOR
--------------------------------------- =
0001 0101 1010 1110 1010 0010 0000 0101(363766277)
這樣作能夠實現了高地位更加均勻地混到一塊兒
參考博客
關於其餘內容介紹
01.關於博客彙總連接
02.關於個人博客