当前位置:网站首页>GetHashCode与Equals
GetHashCode与Equals
2022-08-01 23:52:00 【哀家爆】
需要使用这2个函数的场景,可能是Distinct,可能是Dictionary,都是和HashTable有关,所以有必要先介绍一下哈希表。
哈希表
哈希表是一种数据结构,又叫散列表。这种数据结构有2部分组成:
- 动态数组,即左边一列0-15,每个元素称之为bucket
- 每个bucket,对应了一个单向链表,可能为空链表

新插入的元素通过哈希函数(常用的是除取余法),对应到每一个bucket中,然后再插入到链表尾部。
GetHashCode
其实就是哈希函数的实现,返回值就是哈希码,
equals
找到了bucket之后,还会再判断是否与bucket中已有的元素相等,若不相等,则插入。
执行顺序:
- 先执行GetHashCode用于快速判断,
- 若没有相同的hashcode,则放入一个新的bucket,无需执行equals
- 若有相同的hashcode,则放入相同的hashcode对应的bucket,再执行equals
- 若equals返回true,即相等,则不放入(去重的时候,就认为2个元素相同)
- 若equals返回false,即不相等,则放入
Distinct
情况1
public class Point
{
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
public Point(double x, double y, double z)
{
X= x;
Y= y;
Z= z;
}
public override bool Equals(object obj)
{
var result = base.Equals(obj);
return result;
}
public override int GetHashCode()
{
int result = base.GetHashCode();
return result;
}
}
Point p0 = new Point(1.0001, 2.0001, 3.0001);
Point p1 = new Point(1, 2, 3);
Point p2 = new Point(1, 2, 3);
var points = new List<Point> { p0, p1, p2 };
var newPoints = points.Distinct().ToList();
执行了Distinct去重函数,会自动调用Point自身的GetHashCode和Equals,本例中,我们实现的是默认重载,
- GetHashCode,对于引用类型,返回的是对象的地址,所以只要不是同一个对象,一定返回不同的hashcode,因此在本地中,GetHashCode对于p0、p1、p2返回3个不同的值,所以无需再进行equals的验证。
因此Distinct的结果包含了p0、p1、p2共3个点
情况2
我们来修改一下GetHashCode和Equals,即不管是不是同一个对象,我们只来比较Point类型的实例是不是挨的很近,如果在给定范围内,就认为是相同的点
public class Point
{
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
public Point(double x, double y, double z)
{
X= x;
Y= y;
Z= z;
}
public override bool Equals(object obj)
{
var other = obj as Point;
return Math.Abs(X-other.X)<0.01&&
Math.Abs(Y - other.Y) < 0.01 &&
Math.Abs(Z - other.Z) < 0.01 ;
}
public override int GetHashCode()
{
return 1;
}
}
Point p0 = new Point(1.0001, 2.0001, 3.0001);
Point p1 = new Point(1, 2, 3);
Point p2 = new Point(1, 2, 3);
var points = new List<Point> { p0, p1, p2 };
var newPoints = points.Distinct().ToList();
Distinct执行过程:
- 插入p0点,并计算其hashcode,放入某一个bucket,p0为该bucket中的链表中的第一个元素
- 插入p1点,并计算其hashcode,发现与p0相同,放入p0所在的bucket,再执行equals,发现与p0相同,丢弃
- 插入p2点,并计算其hashcode,发现与p0相同,放入p0所在的bucket,再执行equals,发现与p0相同,丢弃
因此Distinct的结果包含了p0共1个点
情况3
public override bool Equals(object obj)
{
var other = obj as Point;
return X == other.X &&
Y == other.Y &&
Z == other.Z;
}
public override int GetHashCode()
{
return 1;
}
Distinct执行过程:
- 插入p0点,并计算其hashcode,放入某一个bucket,p0为该bucket中的链表中的第一个元素
- 插入p1点,并计算其hashcode,发现与p0相同,放入p0所在的bucket,再执行equals,发现与p0不同,挂在p0后面,此时链表中有2个元素
- 插入p2点,并计算其hashcode,发现与p0相同,放入p0所在的bucket,再执行equals,发现与p0不同,但是与p1相同,丢弃
因此Distinct的结果包含了p0、p1共2个点。
小结
不同的场景,不同的实现。但是如果偷懒GetHashCode返回同样的值,最终通过equal去判断,虽然可以实现想要的效果,但是同一个bucket中最终挂的元素太多的话,链表的查找也是很慢的。。。所以最好在GetHashCode也需要对应重写,尽量在第一步中,就能把不同的元素放到不同的bucket中。
HashSet和Dictionary
Point p0 = new Point(1.0001, 2.0001, 3.0001);
Point p1 = new Point(1, 2, 3);
Point p2 = new Point(1, 2, 3);
HashSet<Point> set = new HashSet<Point>();
set.Add(p0);
set.Add(p1);
set.Add(p2);
Dictionary<Point, string> keyValuePairs = new Dictionary<Point, string>();
keyValuePairs.Add(p0, "0");
keyValuePairs.Add(p1, "1");
keyValuePairs.Add(p2, "2");
调用方式及顺序和Distinct一模一样。
IEqualityComparer
上面是把2个函数的实现,放在Point内部进行的。有时候我们拿到的是别人提供的API,没有权限对其进行修改,那怎么重写呢?
可以实现一个IEqualityComparer:
public class PointComparer : IEqualityComparer<Point>
{
public bool Equals(Point x, Point y)
{
return Math.Abs(x.X-y.X)<0.000001&& Math.Abs(x.Y - y.Y) < 0.01&&Math.Abs(x.Z - y.Z) < 0.01;
}
public int GetHashCode(Point obj)
{
return 1;
}
}
这样子,在调用Distinct的时候,手动指定比较器即可:
var newPoints = points.Distinct(new PointComparer()).ToList();
var set = new HashSet<Point>(new PointComparer());
var keyValuePairs = new Dictionary<Point, string>(new PointComparer());
C++STL中的相应容器
| c# | c++ | 底层数据结构 |
|---|---|---|
| Dictionary | unorder_map | hashtable |
| HashSet | unorder_set | hashtable |
| SortedDictionary(没用过) | map/set | rb_tree |
散列表与红黑树的对比:
相对散列表,二叉查找树好像并没有什么优势,那我们为什么还要用二叉查找树呢?
第一,散列表中的数据是无序存储的,如果要输出有序的数据,需要先进行排序。而对于二叉查找树来说,我们只需要中序遍历,就可以在 O(n) 的时间复杂度内,输出有序的数据序列。
第二,散列表扩容耗时很多,而且当遇到散列冲突时,性能不稳定,尽管二叉查找树的性能不稳定,但是在工程中,我们最常用的平衡二叉查找树的性能非常稳定,时间复杂度稳定在 O(logn)。
比如:红黑树的插入、删除、查找各种操作性能都比较稳定。对于工程应用来说,要面对各种异常情况,为了支撑这种工业级的应用,我们更倾向于这种性能稳定的平衡二叉查找树
第三,笼统地来说,尽管散列表的查找等操作的时间复杂度是常量级的,但因为哈希冲突的存在,这个常量不一定比 logn 小,所以实际的查找速度可能不一定比 O(logn) 快。加上哈希函数的耗时,也不一定就比平衡二叉查找树的效率高。
第四,散列表的构造比二叉查找树要复杂,需要考虑的东西很多。比如散列函数的设计、冲突解决办法、扩容、缩容等。平衡二叉查找树只需要考虑平衡性这一个问题,而且这个问题的解决方案比较成熟、固定。
性能稳定的平衡二叉查找树
第三,笼统地来说,尽管散列表的查找等操作的时间复杂度是常量级的,但因为哈希冲突的存在,这个常量不一定比 logn 小,所以实际的查找速度可能不一定比 O(logn) 快。加上哈希函数的耗时,也不一定就比平衡二叉查找树的效率高。
第四,散列表的构造比二叉查找树要复杂,需要考虑的东西很多。比如散列函数的设计、冲突解决办法、扩容、缩容等。平衡二叉查找树只需要考虑平衡性这一个问题,而且这个问题的解决方案比较成熟、固定。
边栏推荐
- DOM 事件及事件委托
- Artifact XXXwar exploded Artifact is being deployed, please wait...(已解决)
- ELK日志采集
- Thymeleaf简介
- Deep Learning Fundamentals - Numpy-based Recurrent Neural Network (RNN) implementation and backpropagation training
- 问题解决方式了
- Building a cloud-native DevOps environment
- numpy.where
- 20220725资料更新
- 【MySQL系列】 MySQL表的增删改查(进阶)
猜你喜欢

Deep Learning Fundamentals - Numpy-based Recurrent Neural Network (RNN) implementation and backpropagation training

A brief analysis of mobile APP security testing in software testing, shared by a third-party software testing agency in Beijing

Excel导入和导出

Docker实践经验:Docker 上部署 mysql8 主从复制
![[Camp Experience Post] 2022 Cybersecurity Summer Camp](/img/1e/716bafc679dc67d3d54bcc21a3b670.png)
[Camp Experience Post] 2022 Cybersecurity Summer Camp

@Resource和@Autowired的区别

技术分享 | 接口测试中如何使用Json 来进行数据交互 ?

Flink Yarn Per Job - Yarn应用

GIF制作-灰常简单的一键动图工具

在MySQL登录时出现Access denied for user ‘root‘@‘localhost‘ (using password YES) 拒绝访问问题解决
随机推荐
Flink Yarn Per Job - Yarn应用
oozie startup error on cdh's hue, Cannot allocate containers as requested resource is greater than maximum allowed
Share an interface test project (very worth practicing)
云原生DevOps环境搭建
Win10安装DBeaver连接MySQL8、导入和导出数据库详细教程
mysql8安装make报错如何解决
Thinkphp 5.0.24变量覆盖漏洞导致RCE分析
6133. Maximum number of packets
Department project source code sharing
color transparency parameter
cdh6 opens oozieWeb page, Oozie web console is disabled.
Artifact XXXwar exploded Artifact is being deployed, please wait...(已解决)
2022还想上岸学习软件测试必看,测试老鸟的肺腑之言...
辛普森悖论
Architecture basic concept and nature of architecture
Additional Features for Scripting
如何进行数据库备份
Convert LocalDateTime to Date type
1个月写900多条用例,二线城市年薪33W+的测试经理能有多卷?
yay 报错 response decoding failed: invalid character ‘<‘ looking for beginning of value;