[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()))
   }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

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