HashMap如何以及何时初始化entrySet并向其中添加值

时间:2017-01-18 08:10:59

标签: java hashmap

我发现了一个非常神奇的东西,简单的代码如下:

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")

字段变量 table entrySet 的内容如下所示。  enter image description here 那么这是我的第二个问题:将此值添加到entrySet中? 它似乎应该是 put 方法实现这些东西。 以下是 put 方法。

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 并找到只有一个主题。

那我错过了什么?调试过程中是否存在问题?希望我清楚地描述我的问题,一切都会被欣赏。

添加:我的java版本是 1.8.0_77 ,eclipse版本是 4.6.1 4.5.1

3 个答案:

答案 0 :(得分: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 = []

如您所见:如果没有null被调用并且在它之后被初始化,则entrySet实际上仍为toString()

这同样适用于您的第二个问题。如果你“反思地”看这些值:

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

你得到的,正如所料:

entrySet = null

答案 1 :(得分: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个线程(除非您明确创建它们) 如果您想轻松调试,请在

上设置watchpoint
transient Set<Map.Entry<K,V>> entrySet;

在HashMap中。

答案 2 :(得分:0)

entrySet只是一个EntrySet实例,没有在其中初始化或添加值的地方,大多数导入函数在for-each中都用作迭代器,因为它扩展了Iterable