HashMap 首次扩容问题
来源:9-4 HashMap与ConcurrentHashMap解析

慕大侠
2018-04-21
Map<Integer, String> map = new HashMap<Integer, String>(); for (int i = 0; i <20; i++) { map.put(i, "Python " + i); }
老师 我用上面的代码 debug了一下源码 , (在jdk7 环境下) , 第一次进入 resize() 方法的时候是 new HashMap 的时候, 这里不太明白 , 你不是说扩容是发生在 初始容量 * 加载因子 的时候吗, 为什么首次new HashMap 就开始了 resize() , 并没有等到 put 了 12 个元素后 执行 resize , 倒是put了 16 个元素后开始第二次 resize() , 还请老师这里解答一下, 谢谢
5回答
-
本身你这里调用的无参构造方法,不会做resizede,点开进去你可以看到无参构造函数的实现。
我猜测,你是一开始就在HashMap的方法里添加断点了,导致项目刚一运行的时候就会有resize方法被调用。项目启动(debug)时,会有其他的类需要调用HashMap。你需要保证代码明确要执行到HashMap相关代码时再加上断点。这个问题,你往put方法里加个断点,看看key和value就可以明确了
涉及到jdk本身提供的类在调试时都有这个问题,如果断点加的时机不对,就会被其他的数据影响你的调试。你可以尝试着在自己调试的代码前加个log,当这一行log明确执行到的时候,再去底层的类里加上断点。
关于调试,idea里有个小技巧,可以在调试窗口里临时关掉某些断点,需要时再打开,很好用。对于断点加早了,这样会容易很多,不需要挨个取消,而是进来关闭一下,等走到自己要测试的代码时再打开。具体见下图:
012018-04-22 -
Jimin
2018-04-21
我看到了resize的判断是根据threshold
if ((size >= threshold) && (null != table[bucketIndex])) {
而每次resize时,threshold都会赋值为:
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
这个看着没问题啊,就是拿capacity*loadFactor啊042018-04-22 -
Jimin
2018-04-21
调用resize方法的地方呢,应该有个判断
012018-04-21 -
慕大侠
提问者
2018-04-21
-Xms128m -Xmx750m -XX:ReservedCodeCacheSize=240m -XX:+UseConcMarkSweepGC -XX:SoftRefLRUPolicyMSPerMB=50 -ea -Dsun.io.useCanonCaches=false -Djava.net.preferIPv4Stack=true -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow
Jdk 7 源码如下
void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable, initHashSeedAsNeeded(newCapacity)); table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); } /** * Transfers all entries from current table to newTable. */ void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } } }
debug 时发现 HashMap 初始化 的执行顺序 /** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with <tt>key</tt>, or * <tt>null</tt> if there was no mapping for <tt>key</tt>. * (A <tt>null</tt> return can also indicate that the map * previously associated <tt>null</tt> with <tt>key</tt>.) */ public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; 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; } 第二步 /** * Adds a new entry with the specified key, value and hash code to * the specified bucket. It is the responsibility of this * method to resize the table if appropriate. * * Subclass overrides this to alter the behavior of put method. */ void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); // 应该是这里调用的 hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); }
00 -
Jimin
2018-04-21
你好,既然你已经debug源码了,那么把你debug时遇到的方法源码以及jvm相关参数发出来。课程里讲的是默认机制,可以通过参数等影响,影响这个行为的要么是你的源码,要么就是jvm参数,发出来分析一下就可以了
012018-04-21
相似问题