请稍等 ...
×

采纳答案成功!

向帮助你的同学说点啥吧!感谢那些助人为乐的人

HashMap 首次扩容问题

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里有个小技巧,可以在调试窗口里临时关掉某些断点,需要时再打开,很好用。对于断点加早了,这样会容易很多,不需要挨个取消,而是进来关闭一下,等走到自己要测试的代码时再打开。具体见下图:

https://img1.sycdn.imooc.com//szimg/5adc1985000154f301100494.jpg


0 回复 有任何疑惑可以回复我~
  • 提问者 慕大侠 #1
    好吧 我确实是一开始就添加了断点 , 谢谢老师 辛苦了
    回复 有任何疑惑可以回复我~ 2018-04-22 13:36:27
Jimin 2018-04-21 21:15:02

我看到了resize的判断是根据threshold
if ((size >= threshold) && (null != table[bucketIndex])) {

而每次resize时,threshold都会赋值为:
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);

这个看着没问题啊,就是拿capacity*loadFactor啊

0 回复 有任何疑惑可以回复我~
  • 提问者 慕大侠 #1
    这里是没问题,  我的疑问是是为什么 HashMap 初始化的时候就去做了resize() 扩容操作, 初始的时候不是还没有给map 添加元素吗, 也就是说也没达到capacity*loadFactor  这个值啊
    回复 有任何疑惑可以回复我~ 2018-04-22 10:53:52
  • loubobooo 回复 提问者 慕大侠 #2
    刚开始扩容,是因为初始化是,你没给它赋容量操作吧才去做的resize
    回复 有任何疑惑可以回复我~ 2018-04-22 15:17:11
  • 初始化时
    回复 有任何疑惑可以回复我~ 2018-04-22 15:17:45
Jimin 2018-04-21 20:41:02

调用resize方法的地方呢,应该有个判断

0 回复 有任何疑惑可以回复我~
  • 提问者 慕大侠 #1
    我又补充了
    回复 有任何疑惑可以回复我~ 2018-04-21 20:52:31
提问者 慕大侠 2018-04-21 20:37:41
-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);
}


0 回复 有任何疑惑可以回复我~
Jimin 2018-04-21 20:27:57

你好,既然你已经debug源码了,那么把你debug时遇到的方法源码以及jvm相关参数发出来。课程里讲的是默认机制,可以通过参数等影响,影响这个行为的要么是你的源码,要么就是jvm参数,发出来分析一下就可以了

0 回复 有任何疑惑可以回复我~
  • 提问者 慕大侠 #1
    我把 参数和源码贴出来了 老师麻烦看一下
    回复 有任何疑惑可以回复我~ 2018-04-21 20:38:23
问题已解决,确定采纳
还有疑问,暂不采纳
意见反馈 帮助中心 APP下载
官方微信