[122]java中为什么需要hashcode函数

背景

关于hashcode与equals的使用规范,我们已经知道(网上有很多相关介绍)。
1.必须使用同一属性来生成equals和hashcode方法。
2.必须同时重写hashcode和equals方法。

那么为什么会有这样的约定,其背后整个场景逻辑是怎么样的。

什么是hash

关于hash散列表 wiki上的定义如下:
散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。
比如有如下集合(1:a ,2:b ,3:c ,4:d ,5:e ,6:f ,7:g ,8:h),我想知道键值5对应的value是什么。我只能一个个遍历集合遇到key=5的话把结果返回。
但是如果对象(key:value)存放位置是根据hash算出来的。比如location=hash(key%/20),此时我们不需要遍历集合直接就能够索引到5:e 这个数据。其本质上是给集合中给个元素赋予了 位置信息(知识)

hashcode 用来干嘛

"什么是hash"中描述了hash表的逻辑,hashcode也是同样的逻辑。通过hashcode我们可以知道数据在集合中的位置(这里说的位置就是逻辑位置其与物理节点对应,具体实现细节该文不做详解)。

如java中Hashtable就是通过hashcode来定位每个元素的,具体逻辑查看如下代码(关于hashtable的知识请见附录一栏):

        int hash = key.hashCode();  //给key计算hashCode(调用具体对象的hashCode)
        int index = (hash & 0x7FFFFFFF) % tab.length; //计算在hashtable entry[]数组的下表位置。

当我要插入的时候就是通过hashcode来算出数组下标的位置,然后遍历该table[index]对应的链表。如果存在则把旧值替换否则增加,具体看如下hashTable.put()方法。

public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
               //通过equals方法判断是否存在,如果存在就替换。
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

     // 不存在的话就把该值添加到 table[index]的第一个节点。
        addEntry(hash, key, value, index);
        return null;
    }

具体addEntry 方法请见附录一栏。

往hashtable/hashset插入数据由于要维持数据的唯一性(hashtable中key唯一,hashset 元素唯一)。首先通过hash判断然后在用equals方法判断。其伪代码如下:

if (object1.hashcode() != object2.hashcode)
//在hashtable数据结构中同一个hashcode的数据放在一条链表上。tab[2]:node1 ->  node2 -> node3
{
   return false;
}
else{
   if (object1.equals() != object2.equals())
  {
    return false;
   }
}

从抽象意义上来说,hashcode可以判断是初步是否一样,通过equals判断是否完全一样。所以重写equals方法的时候必须重写hashcode。如果hashcode与equals方法对属性的判断不一样,可能在hashcode那一层就会走错方向。在hashtable数据结构中就是两个supObject(a,b),subObject(a,b) (subObject extend supObject,其两个equals方法一样), 算出hashcode不一样导致到hashtable entry[]数组中的下标就不同。

tab[3]:supObject
tab[4]:subObject

SupObject 与subObject伪代码见附录:

附录

HashTable.addEntry方法

private void addEntry(int hash, K key, V value, int index) {
        modCount++;

        Entry<?,?> tab[] = table;
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();

            tab = table;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // Creates the new entry.
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e); //tab[index]第一个几点就是刚刚put的新节点。
        count++;
    }

关于hashtable

为了平衡数组不易插入,链表、数组均不易索引的问题,很容易想到将key值转化为数组的下标、建立一个映射(将字符串转化为整数,或者直接就是整数,先这么简单理解,这也可能产生碰撞),然后再通过指针的方式指向具体的元素,即能快速查找,又能方便插入、删除。具体如下图所示


image.png

参考自:https://www.cnblogs.com/mingaixin/p/5156811.html

SupObject 与subObject类伪代码

SupObject 与subObject类如下所示:

class SupObject{
  boolean  equals()
   {
      return (getA() == null ? other.getA() == null : getA().equals(other.getA()))
            && (getB() == null ? other.getB() == null : getB().equals(other.getB()))
   }
}

class SubObject{
  boolean  equals()
   {
      return (getA() == null ? other.getA() == null : getA().equals(other.getA()))
            && (getB() == null ? other.getB() == null : getB().equals(other.getB()))
   }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Java集合类可用于存储数量不等的对象,并可以实现常用的数据结构如栈,队列等,Java集合还可以用于保存具有映射关...
    小徐andorid阅读 1,997评论 0 13
  • 在OSX中安装Mysql如果一旦出现错误,很难卸载,需要手动删除部分Mysql运行和配置文件,如下为删除相关文件的...
    Avro阅读 20,499评论 3 12
  • 昨天有好朋友从老家赶回这座城市,心情郁结,找我闲聊。 他这次回去是为了给他奶奶做十周年祭。我想当然地认为,祭祀这事...
    史闪亮阅读 543评论 5 2
  • 我胖因为我接地气
    颖仔心随阅读 163评论 0 0
  • 集成MobileVLCKit 框架,简单的说。 使用 VLC 获取视频第一帧图片,其实这个在下载库里的 demo ...
    Mory阅读 2,158评论 0 1