HashMap.Entry类为何设hash字段?添加/检索条目时为何校验hash?
嘿,这两个问题问到点子上了,都是HashMap底层性能优化的关键细节,我来给你掰扯清楚:
核心原因就是避免重复计算hash值,大幅提升性能。
你想啊,HashMap的核心逻辑是靠key的hash值来确定元素存放在哪个桶里的。如果Entry不把这个hash值存下来,那每次需要用到它的时候——比如在链表/红黑树里遍历匹配元素、或者扩容时重新计算桶位置——都得重新调用key.hashCode()来生成hash值。虽然hashCode()一般实现得都比较高效,但架不住频繁调用啊:当链表很长、或者红黑树节点数量多的时候,重复计算的开销会积少成多,拖慢整体性能。
还有个额外的好处:如果不小心用了可变对象当key(虽然强烈不推荐这么做),存下来的hash值是插入时的原始值,能保证后续的查找、删除操作还能基于插入时的桶位置去定位,不会因为key的属性变化导致hashCode改变,进而找不到原来的Entry。
这得从HashMap的查找逻辑说起——hash值是快速过滤的第一道关卡,equals是最终确认的第二道关卡,两者配合才能保证效率。
HashMap的查找流程是:先通过hash值找到对应的桶,再在桶的链表/红黑树里找匹配的Entry。如果只靠equals()来比对,意味着你得把桶里所有元素都挨个用equals()跟目标key比一遍。但equals()通常要比对对象的多个字段,比单纯的整数hash值对比慢得多。
所以实际流程是:
- 先对比两个key的hash值:如果hash值不一样,根据Java的约定(
equals()返回true的两个对象,hashCode()必须相等),这两个key肯定不相等,直接跳过; - 只有当hash值相等的时候,才会调用
equals()做精确比对。
举个直观的例子:假设某个桶里有100个元素,其中只有1个的hash值和目标key相同,那我们只需要调用1次equals(),而不是100次——这性能提升可不是一星半点。
另外,就算遇到hash碰撞(两个不同key的hash值相同),这时候也必须靠equals()来区分,所以hash值的校验是必不可少的前置优化步骤。
内容的提问来源于stack exchange,提问作者Priya

