哈希映射码--------hashCode

探讨了hashCode方法的重要性及其与equals方法的一致性问题。通过实例展示了当仅重写equals方法而不重写hashCode方法时可能导致的问题,并提供了正确的实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

返回对象的哈希码值。该值用于哈希码映射计算中,如Hashtable类对对象进行存储、提取等操作就是基于该对象的哈希码的值的。关于哈希码,Java API文档有如下几条常规协定。

·在Java应用程序执行期间,如果在equals方法比较中所用的信息没有被修改,那么在同一个对象上多次调用hashCode方法时,必须一致地返回相同的整数。但如果多次执行同一个应用时,不要求该整数必须相同。

·如果两个对象通过调用equals方法是相等的,那么着两个对象调用hashCode方法必须返回相同的整数。

·如果两个对象通过调用equals方法是不相等,不要求这两个对象调用hashCode方法必须返回不同的整数。但是,程序员应该意识到对不同的对象产生不同的哈希码值可以提高哈希表的性能。

在Object类中,对于不同的对象,hashCode方法会返回不同的哈希码值,该值通常是根据对象的地址计算而来的。

根据协议第二点,当子类重写了equals方法时,一定要同时重写hashCode方法,否则可能会使得哈希表无法正常得到我们期待的效果。

在下例中:

运行结果:

在main方法中定义了两个HashMap,一个以Box1对象为键,一个以String对象为键。可知box1与box2不相等,box1与box3相等,s1与s2不相等,s1与s3相等。

当map2将s1 作为键(key)存储值(value)"value1"后,通过以s3为键也可以取回值"value1",但是当map1将box1作为键存储值"box1"后,通过box3为键却无法将值"box1"返回。

原因很简单,就是因为String类中在重写了equals方法的同时,也重写了hashCode方法,使得当equals方法返回true时,hashCode的值也相等,而Box1类只重写了equals方法。两个Box1类的对象box1与box3虽然相等(equals返回true),但是两个对象的hashCode仍然不同。于是,当对象参与HashMap的存储与提取时,就无法得到期望的结果。

由于String类重写了hashCode方法,使得相等的String对象(以equals方法来判断)的哈希码值也相同,这样,s1与s3也就能够产生相同的哈希码值来计算哈希表的映射地址,以s1作为键在哈希表中存储值,通过s3一样可以成功取得该值。这就好比有两把相同的尺A与B,我们通过A尺在距离门前5m处埋下一箱宝藏,通过B尺测量5m同样可以取出这厢宝藏一样。

Box1类只重写了equals方法,而没有重写hashCode方法,这使得两个相等的Box1对象的哈希码值也不相同(违背了hashCode方法的第二点协定)。Box1类使用的是从Object类继承而来的hashCode方法,而Object类的hashCode方法通常是根据对象的地址来计算哈希码值的,所以不同的对象一般情况下哈希码值也不会相同。

因此,只要在类中重写equals方法,就一定要重写hashCode方法,通常情况下,应该尽量使不同的对象产生不同的哈希码值(第三点协定),而多数对象都是根据成员变量来判断对象是否相同,这就可以使用成员变量来计算哈希码值。

这也意味着,用来计算哈希码值的成员变量应该与equals方法中用来比较对象是否相等所使用的成员变量相同,或者为equals方法的子集。

例如修正上面的例子:

这样一来,使用equals方法判断相等的两个对象在调用各自的hashCode方法时也一定会产生相同的哈希码值。

### 散列表查找算法的实现与数据结构 #### 1. 散列表的基本概念 散列表是一种基于键值映射的数据结构,其核心思想是通过哈希函数将键(Key)快速转换为存储位置(Index),从而实现在接近 O(1) 的时间内完成插入、删除和查找操作[^5]。 #### 2. 散列表的核心组成 散列表通常由以下几个部分构成: - **哈希函数**:负责将输入的关键字转化为数组索引。 - **冲突解决机制**:由于不同的关键字可能被映射到相同的索引上,因此需要设计一种策略来处理这种情况。 - **装载因子**:表示当前已使用的空间比例,用于判断是否需要动态调整散列表大小以优化性能。 具体来说,在 C/C++ 中可以定义如下散列表结构[^2]: ```c #define SUCCESS 1 #define UNSUCCESS 0 #define HASHSIZE 12 /* 定义散列表长 */ #define NULLKEY -32768 typedef struct { int elem; /* 数据元素存储基址,动态分配数组 */ int count; /* 当前数据元素个数 */ } HashTable; int m = 0; /* 散列表表长,全局变量 */ ``` #### 3. 插入操作的具体实现 在向散列表中插入新数据时,`Create` 函数会先计算目标索引并将其加入相应链表头部。以下是伪代示例[^1]: ```c void Create(int key, int value, HashTable *table) { int index = hashFunction(key); // 使用哈希函数获取索引 Node* newNode = malloc(sizeof(Node)); // 创建节点 newNode->key = key; newNode->value = value; if (table->elem[index] == NULLKEY) { table->elem[index] = newNode; // 如果该槽为空,则直接赋值 } else { insertAtHead(newNode, &table->elem[index]); // 否则插入至链表头 } } ``` #### 4. 查找过程详解 对于给定的关键字 `K` ,可以通过调用预先设定好的哈希函数获得它应该位于哪个桶里;接着遍历这个桶里的所有项直到找到匹配项或者确认不存在为止: ```c bool Search(int key, HashTable *table){ int index = hashFunction(key); Node* current = table->elem[index]; while(current != NULL && current->key != key){ current = current->next; } return (current!=NULL)? true : false ; } ``` 上述逻辑假设每个桶都维护了一个单向链接清单用来保存溢出记录——即那些因碰撞而无法存放在原始位置上的条目们。 #### 5. 处理哈希冲突的方法 当两个不同关键词经过相同哈希运算得出一致结果时就会发生所谓的“哈希冲撞”。针对这一现象有多种解决方案可供选择,其中包括但不限于开放寻址法及其变种形式如线性探查、平方探查以及双倍散列等技术手段还有分离链接法则采用额外指针域构建外部链条连接起这些互相干扰的对象实例形成独立序列加以管理[^3]. 另外值得注意的是Java语言内部已经内置好了完整的对象级hashCode方法支持任意自定义类型的属性参与组合生成唯一标识符以便于高效检索定位所需资源实体[^4]. ### 结论 综上所述,我们了解到如何创建基本框架下的简单版Hash Map原型并通过适当扩展可进一步提升其实用价值满足更多实际应用场景需求.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值