一道面试题看 HashMap 的存储方式

我们公司招人喜欢问算法题和一些基础知识。今天我们一个面试官在面试候选人之前在办公室对我们说他准备问一个这样的问题:

在 HashMap 中存放的一系列键值对,其中键为某个我们自定义的类型。放入 HashMap 后,我们在外部把某一个 key 的属性进行更改,然后我们再用这个 key 从 HashMap 里取出元素,这时候 HashMap 会返回什么?

我们办公室几个人答案都不一致,有的说返回null,有的说能正常返回value。但不论答案是什么都没有确凿的理由。我觉得这个问题挺有意思的,就写了代码测试。结果是返回null。需要说明的是我们自定义的类重写了 hashCode 方法。我想这个结果还是有点意外的,因为我们知道 HashMap 存放的是引用类型,我们在外面把 key 更新了,那也就是说 HashMap 里面的 key 也更新了,也就是这个 key 的 hashCode 返回值也会发生变化。这个时候 key 的 hashCode 和 HashMap 对于元素的 hashCode 肯定一样,equals也肯定返回true,因为本来就是同一个对象,那为什么不能返回正确的值呢?

先来看看一段测试代码:

先解释一下测试代码做到事。定义了一个person类,就两个属性。重写了 hashCode 方法,还有一套geter和seter,没什么特别。测试类里面先创建了三个person对象作为 key 。打印各个 key 的 hashCode 值。然后三个元素放到 HashMap ,接着更新其中一个 key 的name属性,最后去取这个 key 的value。

public class Person {
 
    private String name;
    private int height;
 
    @Override
    public int hashCode() {
        System.out.println(this.name + ": HashCode() invoked!");
        return this.name.hashCode() + this.height;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getHeight() {
        return height;
    }
 
    public void setHeight(int height) {
        this.height = height;
    }
 
    @Override
    public String toString() {
        return "Name:" + this.name + "; height:" + this.height;
    }
}

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
 
public class HashmapTest {
 
    public static void main(String[] args) {
 
        Map<Person, String> testMap = new HashMap<Person, String>();
 
        Person p1 = new Person();
        p1.setName("Jakie");
        p1.setHeight(165);
 
        Person p2 = new Person();
        p2.setName("Jerry");
        p2.setHeight(175);
 
        Person p3 = new Person();
        p3.setName("Torres");
        p3.setHeight(160);
 
        System.out.println(p1 + ";hashcode:" + p1.hashCode() + "\n");
        System.out.println(p2 + ";hashcode:" + p2.hashCode() + "\n");
        System.out.println(p3 + ";hashcode:" + p3.hashCode() + "\n");
 
        System.out.println("************************");
        System.out.println("putting object into map");
        testMap.put(p1, "p1");
        testMap.put(p2, "p2");
        testMap.put(p3, "p3");
 
        System.out.println("************************");
        p2.setName("Jerry is now kelly");
 
        System.out.println("P2 hashcode after update:");
        System.out.println(p2 + ";hashcode:" + p2.hashCode() + "\n");
 
        System.out.println("**************************");
        System.out.println("Hash Code of elements in HashMap");
        for (Entry<Person, String> entry : testMap.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue() + ":"
                    + entry.getKey().hashCode());
            System.out.println();
            if (entry.getKey().getName().equals("Jakie")) {
                System.out.println("Jakie in map is the original jakie "
                        + (entry.getKey() == p1));
            } else if (entry.getKey().getName().equals("Jerry is now kelly")) {
                System.out
                        .println("Jerry is now kelly in map is the original Jerry "
                                + (entry.getKey() == p2));
            }
        }
 
        System.out.println("**********************");
        String p = testMap.get(p2);
        System.out.println("Final Result:" + p);
    }
}
Name:Jakie; height:165;hashcode:71336629
 
Name:Jerry; height:175;hashcode:71462829
 
Name:Torres; height:160;hashcode:-1784098647
 
************************
putting object into map
************************
P2 hashcode after update:
Name:Jerry is now kelly; height:175;hashcode:-711681872
 
**************************
Hash Code of elements in HashMap
Name:Jerry is now kelly; height:175:p2:-711681872
 
Jerry is now kelly in map is the original Jerry true
Name:Jakie; height:165:p1:71336629
 
Jakie in map is the original jakie true
Name:Torres; height:160:p3:-1784098647
 
**********************
Final Result:null


从输出我们可以知道, key 更新后 hashCode 确实更新了。而且 HashMap 里面的对象就是我们原来的对象。最后的结果是null。

我们来看一下 HashMap 的get方法源代码:

public V get(Object key) {
    if (key == null)
        return getForNullKey();
    int hash = hash(key.hashCode());
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
            return e.value;
    }
    return null;
}

可以看到先取得了一个table,这个table实际上是个数组。然后在table里面找对应 key 的value。找的标准就是hash等于传入参数的hash, 并且满足另外两个条件之一:k = e.key,也就是说他们是同一个对象,或者传入的 key 的equal目标的 key 。我们的问题出在那个hash(key.hashCode()),可以看到 HashMap 在存储元素时是把 key 的 hashCode 再做了一次hash。得到的hash将最终作为元素存储位置的依据。对应到我们的情况:第一次存储时,hash函数采用key.hashCode作为参数得到了一个值,然后根据这个值把元素存到了某个位置。

当我们再去取元素的时候,key.hashCode的值已经出现了变化,所以这里的hash函数结果也发生了变化,所以当它尝试去获得这个 key 的存储位置时就不能得到正确的值,导致最终找不到目标元素。要想能正确返回,很简单,把Person类的 hashCode 方法改一下,让它的 hashCode 不依赖我们要修改的属性,但实际开发中肯定不能这么干,我们总是希望当两个对象的属性不完全相同时能返回不同的 hashCode 值。所以结论就是当把对象放到 HashMap 后,不要去修改 key 的属性。

以上都是很基础的东西,但或许我们很多时候都没注意到,了解这些基础可以避免一些很诡异的bug。纯属抛砖引玉,如有谬误请海涵和指出。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HashMapJava中的一个常用的数据结构,它是基于哈希表实现的。在存储过程中,首先会计算元素的hashcode值,然后根据hashcode值进行与操作,得到元素应该存放在哈希表中的桶的索引值。如果桶中没有冲突,即没有其他元素存放在同一个桶中,那么元素就直接插入到该桶中。如果桶中有冲突,即已经有其他元素存放在同一个桶中,那么元素会以链表的形式插入到该桶中。当链表的长度达到一定阈值(一般为8),并且当前HashMap的容量大于64时,链表会被转换为红黑树,以提高查找效率。如果当前容量小于64,则会进行扩容操作,以保证哈希表的负载因子不会过高。 在存储过程中,HashMap还会判断元素的hashcode是否相同,如果hashcode相同,则会使用equals方法判断元素是否重复。如果元素重复,那么新元素将会覆盖原有的元素。因为HashMap不允许存储重复元素。 总结来说,HashMap是通过计算元素的hashcode值来确定元素在哈希表中的位置,并使用链表或红黑树解决哈希冲突问题。同时,在存储过程中还会判断元素是否重复,并进行相应的处理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [java面试题:讲一讲hashMap](https://blog.csdn.net/weixin_44844089/article/details/117455417)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值