[C#] 阅读Hashset的一些感想

Equals和GetHashCode


在了解Hashset之前,先看看代码实现。在Hashset中,要用到两个函数:Equals和GetHashCode。因为Equals的实现,同时也要重写GetHashCode方法。

那么,为什么要重写GetHashCode函数呢?且看看官方警告:如果重写 GetHashCode 方法,您应重写 Equals, ,反之亦然。如果被重写 Equals 方法将返回 true 两个对象是否相等,被重写的测试时GetHashCode 方法必须返回两个对象相同的值。这里很明确说到,GetHashCode就是为了能让Equals相同的两个对象,有相同的Hash值。


Hashset存储原理


Hashset类中有多个成员变量,在存储中需要我们值得注意的就是 int[] buckets、Slot[] slots、int freeIndex、int lastIndex等,分别标记了对应哈希值的index、Hashset对象集合、空的位置和集合最后一个位置。其中,Slot是个结构体,包涵3个成员:int hashcode、T value、int next,分别是哈希值、对象和下一个(相同Hash值)对象的位置。

当我们查找一个对象,是否在Hashset中时,会利用该对象的GetHashCode,通过计算得到对应的bucket值(bucket存储插入操作最后一个对象在slot中的位置,稍后描述),然后再利用Equals来比较对象是否相等,不等则一直比较至Next=0为止。

所以说,Hashset是基于数组和链表管理的。


插入和删除


插入的时候,先查找集合中是否有相同变量(见存储原理),若没有,则开始插入新成员。首先看freeIndex是否有值(该值仅在删除操作后产生),若有就用这个位置,若没有,则用lastIndex(最后一个位置,用完后+1)。因而,找到了插入位置后,开始计算该slot的值:hashcode由GetHashCode产生,value就是新成员,而Next则是用HashCode计算对应位置中的buckets值(存储相同Hashcode值的对象在slots中的位置),并刷新bucket值为当前slot。因而我们知道,由于Next的计算机制,新插入的对象,会覆盖原来buckets的值。

删除操作,同插入一样,也要查找到对象。如果是bucket中第一个对象,在删除对象同时,bucket修改为该对象Next值,否则,则修改上一个对象的Next为该对象的Next(相当于将上个对象指针直接指向下一个对象)。然后,删除的slot值需要修正:hashcode=-1,value=default,next=freeList。所以我们知道,空位置也形成了一个链表。

所以,可以看出HashCode管理起来,也需要一点时间的。但是,由于插入和删除都用了数组+链表,故速度还是很快的。


遍历


遍历使用foreach。这里要说一点,遍历的顺序和插入的顺序可能会不相同(如,对插入第一个数据删除,再插入,则会得到该现象)。Hashset实现了IEnumerable接口,并通过version变量,确保数据在遍历中不发生变化(操作会改变version值)。

相信大家都很了解IEnumerable接口了吧。还有个泛型接口IEnumerable<T>。这里说下Current,在两个接口中都要实现,而非泛型接口Current是要用到的,需要完成“抛出异常”功能(索引超出则抛出异常),并调用了泛型接口Current实现。

好,回归遍历:MoveNext 就是对slot进行顺序遍历了,如果hashcode>=0,则说明输出有效。


总结


总的来说,Hashset通过GetHashCode值(hash相等才会再比较equal)和Equal方法确定是否有相同变量,没有则可以插入。存储是通过数组和链表结合而完成的。

如果学过了算法与数据结构应该很明白其原理了。当初我记得还有什么二次哈希算法来者的,反正好久没再接触了。

本人小白,本文章仅作学习总结,也作交流使用。。

如有错误,欢迎指点,谢谢!


插入操作的源代码


    private bool AddIfNotPresent(T value)
        {
            int freeList;
            if (this.m_buckets == null)
            {
                this.Initialize(0);
            }
            int hashCode = this.InternalGetHashCode(value);
            int index = hashCode % this.m_buckets.Length;
            int num3 = 0;
            for (int i = this.m_buckets[hashCode % this.m_buckets.Length] - 1; i >= 0; i = this.m_slots[i].next)
            {
                if ((this.m_slots[i].hashCode == hashCode) && this.m_comparer.Equals(this.m_slots[i].value, value))
                {
                    return false;
                }
                num3++;
            }
            if (this.m_freeList >= 0)
            {
                freeList = this.m_freeList;
                this.m_freeList = this.m_slots[freeList].next;
            }
            else
            {
                if (this.m_lastIndex == this.m_slots.Length)
                {
                    this.IncreaseCapacity();
                    index = hashCode % this.m_buckets.Length;
                }
                freeList = this.m_lastIndex;
                this.m_lastIndex++;
            }
            this.m_slots[freeList].hashCode = hashCode;
            this.m_slots[freeList].value = value;
            this.m_slots[freeList].next = this.m_buckets[index] - 1;
            this.m_buckets[index] = freeList + 1;
            this.m_count++;
            this.m_version++;
            if ((num3 > 100) && HashHelpers.IsWellKnownEqualityComparer(this.m_comparer))
            {
                this.m_comparer = (IEqualityComparer<T>) HashHelpers.GetRandomizedEqualityComparer(this.m_comparer);
                this.SetCapacity(this.m_buckets.Length, true);
            }
            return true;
        }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值