C#与Java HashSet差异及C#实现O(1)内容去重方案咨询
为什么Java和C# HashSet的比较逻辑不一样?
- Java的
HashSet核心依赖对象的equals()和hashCode()方法:默认情况下,Object类的这两个方法是按引用比较,但像String、Integer这类内置类都重写了它们,改成按内容比对。如果是自定义类,只要重写这两个方法,HashSet就会按照你定义的内容规则去重、判断contains。 - C#的
HashSet<T>默认用EqualityComparer<T>.Default做比较:对于普通引用类型(除了string这种特殊处理的),这个默认比较器是引用相等,也就是只看对象的内存地址,不管内容是不是一样。所以两个内容相同的不同实例,会被当成不同元素存进去,contains也会返回false。
C#中实现O(1)时间复杂度的内容去重方案
要保持HashSet的O(1)查询/插入性能,得让它用基于内容的相等判断,有三种常用办法:
1. 重写自定义类的Equals()和GetHashCode()
直接在你的类里重写这两个方法,HashSet会自动使用这个逻辑。比如定义一个Person类:
public class Person { public string Name { get; set; } public int Age { get; set; } public override bool Equals(object obj) { if (obj is Person other) { return Name == other.Name && Age == other.Age; } return false; } public override int GetHashCode() { // 结合字段生成哈希值,尽量减少碰撞 return HashCode.Combine(Name, Age); } }
之后用HashSet<Person>的时候,就会按Name和Age的内容去重、判断存在性。
2. 实现IEquatable<T>接口
这个方法比重写Object的Equals更类型安全,HashSet会优先识别这个接口的实现:
public class Person : IEquatable<Person> { public string Name { get; set; } public int Age { get; set; } public bool Equals(Person other) { if (other == null) return false; return Name == other.Name && Age == other.Age; } public override bool Equals(object obj) => Equals(obj as Person); public override int GetHashCode() => HashCode.Combine(Name, Age); }
效果和第一种一样,但避免了类型转换的麻烦。
3. 自定义IEqualityComparer<T>(不修改原类)
如果没法修改目标类的代码,可以写一个独立的比较器,创建HashSet的时候传进去:
public class PersonEqualityComparer : IEqualityComparer<Person> { public bool Equals(Person x, Person y) { if (x == null && y == null) return true; if (x == null || y == null) return false; return x.Name == y.Name && x.Age == y.Age; } public int GetHashCode(Person obj) { if (obj == null) return 0; return HashCode.Combine(obj.Name, obj.Age); } } // 使用方式 var personSet = new HashSet<Person>(new PersonEqualityComparer());
这种方式灵活,不用改动原类的代码,同样能实现O(1)的性能。
内容的提问来源于stack exchange,提问作者Vishal A
相关产品推荐
相关产品推荐

