C#哈希表


1.      为什么要用哈希表

因为哈希表的插入,删除,查找等算法都是常量级的,速度要远远大于基于数组的集合。因此,如果频繁的执行插入,删除,查找操作,应当优先考虑哈希表。

2.      使用哈希表时应主要的问题

       2.1如果重写了GetHashCode,也必须重写Equals。反之亦然。重写Equals时应注意,如果两个对象的值相等,那么他们的哈希值也应相等,譬如String对象。但是两个具有不同哈希值的对象,Equals方法可以返回false,为了哈希处理速度,最好这样做。

举一个具体的例子,请看如下代码:

publicclasshassetTest {

 

    publicstaticvoid main(String[] args) {

        HashSet<Point>pointset=new HashSet<Point>();

        Pointp1=newPoint(3,3);

        Pointp2=newPoint(5,5);

        Pointp3=newPoint(3,3);

       

        pointset.Add(p1);

        pointset.Add(p2);

        pointset.Add(p3);

           

    Console.WriteLine(pointset.Count;//这里输出3,显然不是想要的结果。

}

       class Point{

    publicintx;

    publicinty;

    public Point(int x,int y){

        this.x=x;

        this.y=y;

    }

}


       上述代码中,首先创建一个HashSet哈希表,然后向这个表中添加一些Point对象。这里创建了三个Point对象,然后将他们添加到HashSet中,这时哈希表中有三个元素,显然这不是我们想要的结果,因为p1p2两个对象具有相同的值,二者只能有一个被添加到哈希表中。

这时候通过添加GetHashCodeEquals方法,就能够达到我们想要的结果:哈希表中不具有重复的值。如下:

 

  

    public int GetHashCode() {

        int prime = 31;

        int result = 1;

        result= prime * result + x;

        result= prime * result + y;

        return result;

    }

    public bool Equals(Object obj) {

        if (this == obj)

            return true;

        if (obj == null)

            return false;


        Point other = (Point) obj;

        if (x != other.x)

            return false;

        if (y != other.y)

            return false;

        returntrue;

    }


如果我们删除掉hashcode或者删除掉Equals方法,结果是怎么的呢?答案是哈希表中可能不具有唯一元素。

因为HashSetadd方法内部不仅调用了GetHashCode来检查对象的哈希值,他还调用了equals方法来检查两个对象是否相等,只有当两者同时满足的情况下,新元素才不能被添加到集合中。

简单的说,在一个HashSet中添加一个新对象时,首先会获取这个新对象的哈希值。这个哈希值指出了这个对象应该存储在哪一个槽中。然后根据这个哈希值遍历这个槽,从而找出那个对象。找出这个对象之后,还会进一步的调用这个槽中的对象以及要添加的对象的Equals方法,只有当equals方法返回true时,才说明这个新对象和哈希表中现有的的这个对象“同一个对象”。如果在重写GetHashCode的同时,忘记了重写equals方法,这是调用的就是基类的Equals,如果这个对象没有显示基础任何类,那么调用的就是ObjectEquals方法。Object方法比较的是两个对象的引用,而不是比较对象的内容。因此,当添加的是一个新对象时,并且这个新对象与现有的一个对象具有相同的值,那么就会出问题。

因此,如果重写了hashcode或者equals中的一个,必须还要重写另一个。

 

2.2哈希算法至少应使用一个实例字段,这主要是为了使算法具有良好的随机分布,是哈希表获得最佳的性能。

2.3理想情况下,参与哈希算法的实例字段应该是不可变的,也就是说,这个实例子弹在实例化完对象之后,就不能再改变。

如果向哈希表中添加了一个元素,用户不小心更改了对象的值(参与哈希算法的那个实例字段),用户可能不知道他更改的是参与哈希算法的那个实例字段,在大多数时候,用户甚至都不知道他更改了这个对象。

一旦用户更改了这个参与哈希算法的实例字段的值,这对象就有了一个新的哈希值。当他再次使用这个更改后的对象去查找哈希表时,这个新的哈希值就会与旧的哈希值不匹配,从而导致一些不易察觉的问题。譬如当要删除这个对象时,我们以为删除了,事实上却没有删除,从而导致可能的内存泄露问题。为了避免这种情况发生,在删除的时候要测试一下是否删除成功,如下:

        Booleanisremoved= pointset.remove(p1);

        if(isremoved){

            //.....

        }else{

            //。。。。

        }

内存泄露还不是最重要的问题,最重要的是结果会出乎我们的意料,即使调试也很难排查出这种错误。

 

如果,我们能够保证参与哈希算法的实例字段是个常量,那么就可以防止上述所发生的种种问题。因为常量在对象创建完之后就再也不能更改,如果我们的代码无意中更改了这个常量值,编译器会提示错误的。

 

最后再强调一次,向哈希表中添加一个元素之后,不能再修改参与哈希算法的实例字段,但是可以修改其他实例字段。因为这些字段没有参加哈希运算,所以你怎么修改它对于哈希表来说都没有什么影响。如果我们向point类中新添加一个字段z,但不要修改GetHashCode以及Equals方法,那么我们修改这个z值,对于哈希表来说没有什么影响的。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值