【.NET面试题】什么是Hash,Hash的常见应用

【.NET面试题】什么是Hash,Hash的常见应用

  1. 知识储备

哈希(也叫散列)是一种查找算法(可用于插入),哈希算法希望能做到不经过任何比较(发生冲突,还是需要少许比较),通过一次存取就能得到查找的数据。

因此哈希的关键在key和数据元素的存储位置之间建立一个确定的对应关系,每个key在哈希表中都有唯一的地址相对应(形成有限、连续的地址空间),查找时根据对应关系经过一步计算得到key在散列表的位置。

在数学上, 原Key叫做原像,由映射函数h(key)映射的存储位置叫做像;在IT领域,以上存储位置叫哈希地址(散列地址),这个映射过程叫做哈希/散列。

故可以预见:
  ① 不同的key值,由哈希函数h(x) 作用后可能映射到同一个哈希地址, 这就是哈希冲突,冲突发生的概率取决于 定义的哈希函数

② 由哈希表作用后的哈希地址需要空间存储,这一系列连续相邻的地址空间叫哈希表、 散列表。

处理哈希冲突可分为两大类:

(1)开散列法发生冲突的元素存储于数组空间之外。可以把“开”字理解为需要另外“开辟”空间存储发生冲突的元素, 又称【链地址法】

(2)闭散列法发生冲突的元素存储于数组空间之内。可以把“闭”字理解为所有元素,不管是否有冲突,都“关闭”于数组之中,闭散列法又称【开放定址法】,意指数组空间对所有元素,不管是否冲突都是开放的

哈希表是用数组实现的一片连续的地址空间,两种冲突解决方案的区别在于发生冲突的元素是存储在这片数组的空间之外还是空间之内

  1. 看图说话
    哈希表

—以下是开散列法(链地址法)解决冲突的示意图------

从图上看实现【哈希】过程分两部分:

① 哈希函数

收敛函数,不可避免会冲突,需要思考设定一个均衡的哈希函数,使哈希地址尽可能均匀地分布在哈希地址空间

② 构造哈希表 + 冲突链表

装填因子loadfactor :所谓装填因子是指哈希表中已存入的记录数n与哈希地址空间大小m的比值,即 α=n / m ,α越小,冲突发生的可能性就越小;α越大(最大可取1),冲突发生的可能性就越大。
另一方面,α越小,存储窨的利用率就越低;反之,存储窨的利用率就越高。为了既兼顾减少冲突的发生,又兼顾提高存储空间的利用率,通常把α控制在0.6~0.9的范围之内

哈希在.Net中的应用

Object基类中有GetHashCode方法,HashCode是一个数字值,用于在【基于哈希特性的集合】中插入和查找某对象;GetHashCode方法为需要快速检查对象相等性的算法提供此哈希代码;

Do not test for equality of hash codes to determine whether two objects are equal. (Unequal objects can have identical hash codes.) To test for equality, call the ReferenceEquals or Equals method. (重要的话要读3遍)

单纯判断【逻辑相等】时,本无所谓重写 GetHashCode方法;但若在基于hash的集合中快速查找/插入某元素,则一定要重写GetHashCode方法。

? 我们看一个实锤:

统计参加Footabll,Basketball 两个球队的所有成员,自然会想到对 footabllTeam, basketballTeam 成员使用Union方法求并集 (A∪B)

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Http;
using System.Collections.Generic;
using System.Linq;
using System.Collections;

namespace Test
{
public class Person
{
public string Name { get; set; }
public int Age { get; set; }

    // 单纯判断逻辑相等,只需重写Equals方法
    public override bool Equals(object obj)
    {
        return Name == (obj as Person).Name;
    }
    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }
}

/// <summary>
/// 体育小组成员
/// </summary>
public class TeamMember
{
    public Person Member { get; set; }
    public string Sport { get; set; } = "footabll";
}
public class Program
{
    static void Main()
    {
        var Beckham = new Person { Name = "Beckham", Age = 21 };
        var Kobe = new Person { Name = "Kobe", Age = 21 };

        var footabllTeam = new List<TeamMember>
        {
            new TeamMember { Member = new Person { Name = "nash", Age=21 } },
            new TeamMember { Member = Beckham }
        };
        var basketballTeam = new List<TeamMember>
        {
            new TeamMember { Member = new Person {  Name = "nash", Age=21 },Sport="basketball" },
            new TeamMember { Member = Kobe,Sport = "basketball" }
        };

        // “==”操作符:判断引用相等
        Console.WriteLine($"足球队[0]和篮球队[0]是不同引用对象,footabllTeam[0].Member == basketballTeam[0].Member输出:{footabllTeam[0].Member == basketballTeam[0].Member}");

        // 对两个内存对象,判断逻辑相等
        Console.WriteLine($"足球队[0]和篮球队[0]逻辑上是一个人,footabllTeam[0].Member.Equals(basketballTeam[0].Member输出:{footabllTeam[0].Member.Equals(basketballTeam[0].Member )}");

        // 统计两个球队中所有队员, hash-base查找/插入,务必重写GetHashCode方法
        var members = footabllTeam.Select(x=>x.Member).Union(basketballTeam.Select(x=>x.Member));
        Console.WriteLine($"总成员人数:{members.Count()} ");
        Console.Read();
    }
}

}
------------------------output----------------------------------------:
足球队[0]和篮球队[0]是不同引用对象,footabllTeam[0].Member == basketballTeam[0].Member输出:False
足球队[0]和篮球队[0]逻辑上是一个人,footabllTeam[0].Member.Equals(basketballTeam[0].Member输出:True
总成员人数:3
  观察Union源码计算A,B并集的实现,内部会构造哈希表Set 快速查找和插入并集元素,故我们需要给元素编写合适的哈希函数。
public static IEnumerable Union(this IEnumerable first, IEnumerable second, IEqualityComparer comparer)
{
if (first == null) throw Error.ArgumentNull(“first”);
if (second == null) throw Error.ArgumentNull(“second”);
return UnionIterator(first, second, comparer);
}

static IEnumerable UnionIterator(IEnumerable first, IEnumerable second, IEqualityComparer comparer)
{
Set set = new Set(comparer);
foreach (TSource element in first)
if (set.Add(element)) yield return element; // Set 便是Union方法内部构造的哈希表
foreach (TSource element in second)
if (set.Add(element)) yield return element;
}

Union方法入口
不是总说没处理过哈希冲突吗?接下来来:
【知识储备】
1.围观链地址法处理哈希冲突:


internal class Set<TElement>
{
        int[] buckets;            //  连续相邻的地址空间,盛放不同冲突链表的容器,俗称哈希桶
        Slot[] slots;             //  用于解决冲突的链表
        int count;
        int freeList;
        IEqualityComparer<TElement> comparer;

        public Set() : this(null) { }

        public Set(IEqualityComparer<TElement> comparer) {
            if (comparer == null) comparer = EqualityComparer<TElement>.Default;
            this.comparer = comparer;
            buckets = new int[7];   // 初始哈希桶和冲突链表长度 都是7
            slots = new Slot[7];
            freeList = -1;
        }

        // If value is not in set, add it and return true; otherwise return false
        public bool Add(TElement value) {
            return !Find(value, true);
        }

        // Check whether value is in set
        public bool Contains(TElement value) {
            return Find(value, false);
        }

        // If value is in set, remove it and return true; otherwise return false
        public bool Remove(TElement value) {
            int hashCode = InternalGetHashCode(value);
            int bucket = hashCode % buckets.Length;
            int last = -1;
            for (int i = buckets[bucket] - 1; i >= 0; last = i, i = slots[i].next) {
                if (slots[i].hashCode == hashCode && comparer.Equals(slots[i].value, value)) {
                    if (last < 0) {
                        buckets[bucket] = slots[i].next + 1;
                    }
                    else {
                        slots[last].next = slots[i].next;
                    }
                    slots[i].hashCode = -1;
                    slots[i].value = default(TElement);
                    slots[i].next = freeList;
                    freeList = i;
                    return true;
                }
            }
            return false;
        }

        bool Find(TElement value, bool add) {
            int hashCode = InternalGetHashCode(value);
            for (int i = buckets[hashCode % buckets.Length] - 1; i >= 0; i = slots[i].next) {
                if (slots[i].hashCode == hashCode && comparer.Equals(slots[i].value, value)) return true;
            }
            if (add) {
                int index;
                if (freeList >= 0) {
                    index = freeList;
                    freeList = slots[index].next;
                }
                else {
                    if (count == slots.Length) Resize();
                    index = count;
                    count++;
                }
                int bucket = hashCode % buckets.Length;
                slots[index].hashCode = hashCode;
                slots[index].value = value;
                slots[index].next = buckets[bucket] - 1;
                buckets[bucket] = index + 1;
            }
            return false;
        }

        void Resize() {
            int newSize = checked(count * 2 + 1);    // 尝试扩容
            int[] newBuckets = new int[newSize];
            Slot[] newSlots = new Slot[newSize];
            Array.Copy(slots, 0, newSlots, 0, count);
            for (int i = 0; i < count; i++) {
                int bucket = newSlots[i].hashCode % newSize;
                newSlots[i].next = newBuckets[bucket] - 1;
                newBuckets[bucket] = i + 1;
            }
            buckets = newBuckets;
            slots = newSlots;
        }

        internal int InternalGetHashCode(TElement value)
{
            //Microsoft DevDivBugs 171937. work around comparer implementations that throw when passed null
            return (value == null) ? 0 : comparer.GetHashCode(value) & 0x7FFFFFFF;
        }

        internal struct Slot
        {
            internal int hashCode;
            internal TElement value;
            internal int next;
        }
    }

因此有最佳实践: 当两对象重写Equal方法返回true时, 请务必重写GetHashCode方法为对象返回相同的hashcode。

话虽如此,写一个合适、均衡的哈希函数还是比较考验算法的。

在一般场景中,经验会帮助你编写哈希函数, 比如以上Person类中,字符串类型Name的HashCode总是相等的。

That‘all 看完了通篇文章的同栈猿,应该就可以回答文章引言 5大提问:
Q1:什么是哈希?

Q2:哈希为什么快?

Q3:你是怎么理解哈希算法利用空间换取时间的?

Q4:你是怎么解决哈希冲突的?

Q5:你有实际用写过哈希算法吗?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
回答: Redis常见面试题包括但不限于以下几个方面: 1. Redis的线程模型是什么?\[1\] Redis在6.0之前是单线程的,6.0之后开始支持多线程。Redis内部使用了基于epoll的多路复用,也可以通过多部署多个Redis服务器来解决单线程的问题。 2. Redis的性能瓶颈是什么?\[1\] Redis的主要性能瓶颈是内存和网络。内存可以通过增加内存条来解决,而网络是一个更大的问题。因此,Redis 6.0引入了多线程的概念,在网络IO处理方面引入了多线程,如网络数据的读写和协议解析等。需要注意的是,执行命令的核心模块仍然是单线程的。 3. Redis的持久化方式有哪些?\[2\] Redis有两种持久化方式:RDB(Redis Database)和AOF(Append Only File)。RDB是将内存中的数据以快照的形式保存到磁盘上,而AOF是将每个写操作追加到文件末尾。可以根据实际需求选择适合的持久化方式。 4. Redis的数据结构有哪些?\[3\] Redis支持多种数据结构,包括字符串(string)、列表(list)、集合(set)、有序集合(sorted set)和哈希(hash)。这些数据结构都支持各种操作,并且这些操作都是原子性的。 5. Redis的使用场景有哪些?\[3\] Redis可以用于多种场景,包括但不限于:缓存、消息队列、计数器、分布式锁、会话管理等。由于Redis是基于内存的NoSQL数据库,读写操作非常快速,因此在需要高性能和低延迟的场景下广泛应用。 希望以上回答能够帮助到您。 #### 引用[.reference_title] - *1* *3* [redis面试题总结(附答案)](https://blog.csdn.net/guorui_java/article/details/117194603)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [精选 21道 Redis 最常问面试题!收藏一波 !](https://blog.csdn.net/w915209092/article/details/126035419)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值