技術問答集錦(一)

1 hashCode和equals方法,在HashMap中如何使用?hashCode和equals方法之間的關係及爲何?

Object的equals和hashCode方法。以下:java

// hashCode()方法默認是native方法;
 public native int hashCode();
 
 // equals(obj)默認比較的是內存地址;
 public boolean equals(Object obj) {
     return (this == obj);
 }
複製代碼

hashCode()方法有三個關注點:數組

關注點1:主要是說這個hashCode方法對哪些類是有用的,並非任何狀況下都要使用這個方法(此時是根本沒有必要來複寫此方法),而是 當你涉及到像HashMap、HashSet(他們的內部實現中使用到了hashCode方法)等與hash有關的一些類時,纔會使用到hashCode方法數據結構

關注點2:推薦按照這樣的原則來設計,即 當equals(object)相同時,hashCode()的返回值也要儘可能相同,當equals(object)不相同時,hashCode()的返回沒有特別的要求,可是也是儘可能不相同以獲取好的性能多線程

關注點3:默認的hashCode實現通常是內存地址對應的數字,因此不一樣的對象,hashCode()的返回值是不同的。併發

在這種缺省實施狀況下,只有它們引用真正同一個對象時這兩個引用纔是相等的。一樣,Object提供的hashCode()的缺省實施經過將對象的內存地址對映於一個整數值來生成。ide

接下來HashMap的源碼分析hashCode和equals方法使用過程:函數

static final Entry<?,?>[] EMPTY_TABLE = {}; 
 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
複製代碼

HashMap內部是由Entry<K,V>類型的數組table來存儲數據的。來看下Entry<K,V>的代碼:源碼分析

static class Entry<K,V> implements Map.Entry<K,V> { 
     final K key; 
     V value; 
     Entry<K,V> next; 
     // key的hashCode方法的返回值通過hash運算獲得的值
     int hash; 

     /** * Creates new entry. */ 
     Entry(int h, K k, V v, Entry<K,V> n) { 
         value = v; 
         next = n; 
         key = k; 
         hash = h; 
     }
     //略
 }
複製代碼

因此咱們能夠畫出HashMap的存儲結構:性能

HashMap存儲結構

圖中的每個方格就表示一個Entry<K,V>對象,其中的橫向則構成一個Entry<K,V>[] table數組,而豎向則是由Entry<K,V>的next屬性造成的鏈表。this

首先看下它HashMap是如何來添加的,即 put(K key, V value)方法:

public V put(K key, V value) { 
     if (table == EMPTY_TABLE) { 
         inflateTable(threshold);
     } 
     if (key == null) return putForNullKey(value); 
     // 位置[1]
     int hash = hash(key);
     // 位置[2]
     int i = indexFor(hash, table.length); 
     for (Entry<K,V> e = table[i]; e != null; e = e.next) { 
         Object k; 
         // 位置[4] 遍歷鏈表,若鏈表已存在一致的對象,則替換
         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 
             V oldValue = e.value; 
             e.value = value;
             e.recordAccess(this); 
             return oldValue; 
         } 
     } 
     modCount++; 
     // 插入
     addEntry(hash, key, value, i); 
     return null; 
 }
複製代碼

重點關注它的存的過程,位置[1]首先就是計算key的hash值,這個hash計算的過程位置[3]便用到了key對象的hashCode方法,以下:

final int hash(Object k) { 
     int h = hashSeed; 
     if (0 != h && k instanceof String) { 
         return sun.misc.Hashing.stringHash32((String) k); 
     } 
     // 位置[3]
     h ^= k.hashCode(); 

     h ^= (h >>> 20) ^ (h >>> 12); 
     return h ^ (h >>> 7) ^ (h >>> 4); 
 }
複製代碼

獲得這個hash值後,位置[2]緊接着執行了int i = indexFor(hash, table.length);就是找到這個hash值在table數組中的索引值,具體方法indexFor(hash, table.length)爲:

static int indexFor(int h, int length) { 
     return h & (length-1); 
 }
複製代碼

**位置[4]判斷是否一致的條件是:**e.hash == hash && ((k = e.key) == key || key.equals(k)),必定要緊緊記住這個條件。

**必須知足的條件1:**hash值同樣,hash值的來歷就是根據key的hashCode再進行一個複雜的運算,當兩個key的hashCode一致的時候,計算出來的hash也是必然同樣的。

**必須知足的條件2:**兩個key的引用同樣或者equals相同。

綜上所述,HashMap對於key的重複性判斷是基於兩個內容的判斷,一個就是hash值是否同樣(會演變成key的hashCode是否同樣),另外一個就是equals方法是否同樣(引用同樣則確定同樣)

因此,hashCode的重寫的原則:當equals方法返回true,則兩個對象的hashCode必須同樣

equals()方法,在get()方法中的使用過程分析:

public V get(Object key) {    
     Node<K,V> e;
     return (e = getNode(hash(key), key)) == null ? null : e.value;
 }
 
 final Node<K,V> getNode(int hash, Object key) {
     Node<K,V>[] tab; 
     Node<K,V> first, e; 
     int n; K k;    
     if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
         // 元素相等判斷
         if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))
             return first;        
         if ((e = first.next) != null) {            
             if (first instanceof TreeNode)                
                 return ((TreeNode<K,V>)first).getTreeNode(hash, key);                 
             do { 
                 // 鏈表上的元素相等判斷 
                 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                     return e;            
             } while ((e = e.next) != null);
         }
     }
     return null;
 }
複製代碼

爲何 Override equals()和hashCode()?

因爲做爲key的對象將經過計算其hashCode來肯定與之對應的value的位置,所以任何做爲key的對象都必須實現 hashCode和equals方法。hashCode和equals方法繼承自根類Object,若是你用自定義的類看成key的話,要至關當心,按照散列函數的定義,若是兩個對象相同,即obj1.equals(obj2)=true,則它們的hashCode必須相同,但若是兩個對象不一樣,則它們的 hashCode不必定不一樣,若是兩個不一樣對象的hashCode相同,這種現象稱爲衝突,衝突會致使操做哈希表的時間開銷增大,hashCode()方法目的純粹用於提升效率,因此儘可能定義好的 hashCode()方法,能加快哈希表的操做。

若是相同的對象有不一樣的hashCode,對哈希表的操做會出現意想不到的結果(期待的get方法返回null),要避免這種問題,只須要牢記一條:要同時複寫equals方法和hashCode方法,而不要只寫其中一個

hashcode與equals方法使用:http://my.oschina.net/xianggao/blog/90110

2 內存泄漏與內存溢出的區別?

Java虛擬機在執行Java程序的過程當中把它所管理的內存劃分爲若干個不一樣的數據區域。這些區域都有各自的用途,以及建立和銷燬的時間,有的區域隨着虛擬機進程的啓動而存在,有些區域則依賴用戶線程的啓動和結束而創建和銷燬。以下圖所示:

JVM運行時數據區域

虛擬機棧在運行時使用一種叫作棧幀的數據結構保存上下文數據。在棧幀中,存放了方法的局部變量表,操做數棧,動態鏈接方法和返回地址等信息。每個方法的調用都伴隨着棧幀的入棧操做。相應地,方法的返回則表示棧幀的出棧操做。若是方法調用時,方法的參數和局部變量相對較多,那麼棧幀中的局部變量表就會比較大,棧幀會膨脹以知足方法調用所需傳遞的信息。所以,單個方法調用所需的棧空間大小也會比較多。

虛擬機棧結構

注意:函數嵌套調用的次數由棧的大小決定。棧越大,函數嵌套調用次數越多。對一個函數而言,它的參數越多,內部局部變量越多,它的棧幀就越大,其嵌套調用次數就會越少。

可達性分析:Java中對內存對象的訪問,使用的是引用的方式。在Java代碼中咱們維護一個內存對象的引用變量,經過這個引用變量的值,咱們能夠訪問到對應的內存地址中的內存對象空間。在Java程序中,這個引用變量自己既能夠存放堆內存中,又能夠放在代碼棧的內存中(與基本數據類型相同)。GC線程會從代碼棧中的引用變量開始跟蹤,從而斷定哪些內存是正在使用的。若是GC線程經過這種方式,沒法跟蹤到某一塊堆內存,那麼GC就認爲這塊內存將再也不使用了(由於代碼中已經沒法訪問這塊內存了)。

可達性分析

經過這種有向圖的內存管理方式,當一個內存對象失去了全部的引用以後,GC 就能夠將其回收。反過來講,若是這個對象還存在引用,那麼它將不會被GC回收,哪怕是Java虛擬機拋出OutOfMemoryError。

Java中的內存泄漏,主要指的是是在內存對象明明已經不須要的時候,還仍然保留着這塊內存和它的訪問方式(引用)

在不涉及複雜數據結構的通常狀況下,Java的內存泄露表現爲一個內存對象的生命週期超出了程序須要它的時間長度。咱們有時也將其稱爲「對象遊離」。

3 深刻HashMap,及併發讀寫狀況下死循環問題?

深刻JDK源碼之HashMap類:http://my.oschina.net/xianggao/blog/386697

HashMap多線程併發問題分析:http://my.oschina.net/xianggao/blog/393990

ConcurrentHashMap深刻分析:http://my.oschina.net/xianggao/blog/212060

HashMap vs ConcurrentHashMap:http://my.oschina.net/xianggao/blog/394213

相關文章
相關標籤/搜索