2017-01-18 54 views
5

我發現一個很神奇的東西,簡單的代碼如下:HashMap中如何和何時初始化的entrySet和增加價值到它

public class Demo{ 
    public static void main(String[] args){ 
     HashMap<String,String> map = new HashMap<String,String>(); 
     map.put("a", "aa"); 
     System.out.println("end"); 
    } 
} 

調用後

HashMap<String,String> map = new HashMap<String,String>(); 

the state of map object 字段變量的entrySet不是null,也就是說它已被初始化。


然後這是我第一次問題,當的entrySet已初始化? 它似乎相關的代碼應該是HashMap中的結構,但低於是它似乎這個構造

public HashMap() { 
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted 
} 

的源代碼中有不存在其初始化的entrySet代碼。

事情繼續下去。 後調用

map.put("a","aa") 

場變量的entrySet的如下拍攝的內容。 enter image description here 然後,這是我的第二個問題:何時將此值添加到entrySet? 看來應該是方法實現了這些東西。 以下是方法。

public V put(K key, V value) { 
    return putVal(hash(key), key, value, false, true); 
} 

它調用putVal方法和下面是一些代碼putVal

final V putVal(...) { 
    .... 
    tab[i] = newNode(hash, key, value, null); 
    .... 
    ++modCount;//after invoke this the entrySet is still empty 
    if (++size > threshold) 
     resize();//this has not been executed 
    afterNodeInsertion(evict);//I debug several times, sometimes before invoke this the entrySet has an Element and sometimes 
    return null; 
}  

調用後

++modCount; 

的entrySet是空 和之前調用

afterNodeInsertion(evict); 

entrySet有一個元素。 但它似乎這兩行之間的代碼與entrySet沒有任何關係。 我想也許存在多個線程運行的entrySet然後我寫與jvm_ti一個小工具,打印的調用下面包類java.util中,發現只有一個線程的線程ID。

那我想念什麼?調試過程中是否存在問題?希望我已經清楚地描述了我的問題,並且一切都會很感激。

地址:我的Java版本是1.8.0_77和Eclipse版本是4.6.1 4.5.1

+1

爲什麼這很重要?它如何影響合同? –

+2

@SkinnyJ它沒有關係,但我想知道實際的細節並遇到一些問題。 –

+0

你可以添加一個斷點到HashMap中的方法entrySet()嗎?我假設輸入集在那裏被初始化。如果您在那裏放置斷點並且線程暫停,您可以看到調用此方法的代碼的堆棧跟蹤。 – toongeorges

回答

6

這是你的調試器愚弄你。調試器視圖調用toString(),實際上調用entrySet()(請參閱AbstractMap.toString())。這就是爲什麼entrySet已經初始化,當你看它。

如果你在那裏通過反射實用工具,例如用下面的代碼:

HashMap<String, String> map = new HashMap<>(); 

Field entrySetField = HashMap.class.getDeclaredField("entrySet"); 
entrySetField.setAccessible(true); 
Object entrySet = entrySetField.get(map); 
System.out.println("entrySet = " + entrySet); 
System.out.println("map.toString() = " + map.toString()); 
entrySet = entrySetField.get(map); 
System.out.println("entrySet = " + entrySet); 

將得到以下的輸出:

entrySet = null 
map.toString() = {} 
entrySet = [] 

正如你可以看到:的entrySet其實還是null如果沒有toString()被調用,之後被初始化。

這同樣適用於您的第二個問題。如果你看一下值 「反思」:

// Starting from where my entrySet is still null 
map.put("key", "value"); 
entrySet = entrySetField.get(map); 
System.out.println("entrySet = " + entrySet); 

你,符合市場預期:

entrySet = null 
+0

這正是調試時發生的情況,與AbstractMap一樣好! – rkosegi

+0

@Roland精彩,但是還有一個額外的問題,如果我在第二種情況下使用'map.toString()',它會打印* [key = value] *而不是* [] *。* entrySet *方法它只是通過默認的構造函數new * EntrySet * Object所以我不知道元素何時被插入到* entrySet *中。 –

+0

你的意思是,entrySet如何知道值?如果是這樣的話:'EntrySet'是一個內部類,因此知道它的容器中的所有內容。它可能隱藏在節點周圍的某處......不想在那裏發現太深的內容。 ;-) – Roland

1

快速查看源代碼發現,它懶洋洋地分配:

public Set<Map.Entry<K,V>> entrySet() { 
    Set<Map.Entry<K,V>> es; 
    return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; 
} 

Reference

我想也許存在幾個線程操作entrySet然後我用jvm_ti寫一個小工具來打印threadID,它調用java.util包下的 類,並且發現只有一個線程。

不,絕對有參與NO線程(除非你明確創建它們) 如果你想輕鬆地調試它,在

transient Set<Map.Entry<K,V>> entrySet; 

內的HashMap設置watchpoint

+0

你近了:) – xenteros

+0

@rkosegi我同意這個方法初始化entrySet,**但**我找到之前執行我的第一個語句'HashMap map = new HashMap ();'這個方法被調用,這與我的* map對象*沒有任何關係。然後這個方法還沒有被調用。 –

+0

@nailfei我很確定這只是另一個HashMap。 –