我的编程空间,编程开发者的网络收藏夹
学习永远不晚

.NET中的HashSet原理是什么

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

.NET中的HashSet原理是什么

这篇文章主要介绍“.NET中的HashSet原理是什么”,在日常操作中,相信很多人在.NET中的HashSet原理是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”.NET中的HashSet原理是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

哈希表原理

HashSet是基于哈希表的原理实现的,学习HashSet首先要了解下哈希表。

哈希表(hash table, 也叫散列表)是根据key直接访问存储位置的数据结构,它通过一个键值的函数,将所需查询的数据映射到表中一个位置来访问,加快了查找速度。

上述函数即为哈希函数,哈希函数应尽量计算简单以提高插入、检索效率;计算得到的地址应尽量分布均匀,以降低哈希冲突;应具有较大的压缩性,以节省内存。常见的哈希函数构造方法有直接定址法、除留余数法、数字分析法等。HashSet采用除留余数法,将元素的HashCode除以某个常数(哈希表Size)的余数作为地址,常数通常选取一个素数。

两个相等的对象的哈希值相同,但两个不等的对象的哈希值是有可能相同的,这就是哈希冲突。处理冲突的方法有开放定址法、链表法、双散列法等。HashSet使用链表法,将冲突元素放在链表中。

哈希表是一种用于高性能集合操作的数据结构,它有如下特点:

  • 无序、不重复;插入、查找时间复杂度为O(1);

  • 不使用索引;

  • 容量不足时自动扩容,但扩容成本高;

  • 可提供很多高性能集合操作,如合并、裁剪等;

HashSet实现

HashSet内置了两个数组,如下。_buckets中存放由哈希函数计算得到的索引值,_buckets中的值从1开始,因此在使用时需要-1。该值即为_entries数组的相对索引,若未发生冲突,指向的值即为待查找元素的相对索引。如果发生了冲突,根据冲突链表也可以快速定位到元素。_entries存放的是Entry对象,Entry类型如下所示。HashCode为元素的哈希值,在查找、插入、删除、扩容等操作时都会用到。Value存储数据。Next在不同时刻有不同的作用,当Entry在列表中时,形成冲突链表,其Next指向冲突链表的下一元素,链表最后一个元素的Next值为-1;若Entry已被列表删除,形成空位链表,其Next指向空位链表的下一元素,空位链表的最后一个元素值为-2。

HashSet还有几个关键成员:_count、_freeList、_freeCount。_count表示添加元素数量,注意它并不是实际存储的元素数量,因为在删除元素时未更新它。_freeList为空位链表头,其值指向被删除的_entries索引,_entries[_freeList].Next指向下一空位的相对位置。_freeCount表示空位数量,列表实际存储的元素数量为_count - _freeCount。

private int[]? _buckets; // _entries索引数组private Entry[]? _entries; // 实体数组private int _count; // 实际存储的元素数量private int _freeList; // 空位链表头索引,初始值-1private int _freeCount; // 空位数量private struct Entry{    public int HashCode;    public int Next;    public T Value;}public int Count => _count - _freeCount; // _count不会记录被删除的元素,因此实际元素数量为_count - _freeCount

HashSet提供了多个构造函数重载,如果不传任何参数,不会初始化_buckets和_entries。当添元素时,会调用Initialize(0)。Initialize方法接受一个int参数,该参数表示需要初始化的列表容量。实际初始化的列表容量为大于等于该值的最小素数。取素数作为列表长度是因为该值作为使用除留余数法构造的哈希函数的除数,对素数求余结果分布更均匀,减少了冲突的发生。

private int Initialize(int capacity){    int size = HashHelpers.GetPrime(capacity); // 获取>=capacity的最小素数    var buckets = new int[size];    var entries = new Entry[size];    // ...    return size;}

查找元素时,首先调用元素的GetHashCode方法计算哈希值,然后调用GetBucketRef方法执行哈希函数运算,获得索引。GetBucketRef的返回值-1为真实索引i,若i为-1,则未找到元素。若i>=0,表示列表中存在与待查找元素哈希值相同的元素,但相等的哈希值并不一定表示元素相等,还要进一步判断HashCode,若HashCode相等,再判断元素是否相等,满足则查找到元素,返回_entries的索引i。

private int FindItemIndex(T item){    // ...    int hashCode = item != null ? item.GetHashCode() : 0;    if (typeof(T).IsValueType)    {        int i = GetBucketRef(hashCode) - 1; // _buckets元素从1开始        while (i >= 0) // 存在与item相同的哈希值,不一定存在item        {            ref Entry entry = ref entries[i];            if (entry.HashCode == hashCode && EqualityComparer<T>.Default.Equals(entry.Value, item))            {                return i; // HashCode相等且元素相等,则查找到元素,返回_entries索引            }            i = entry.Next;            collisionCount++;            // ...        }    }    // ...    return -1;}private ref int GetBucketRef(int hashCode){    int[] buckets = _buckets!;    return ref buckets[(uint)hashCode % (uint)buckets.Length]; // 使用除留余数法构造哈希函数}

插入元素时,首先会查找待插入的元素是否存在,HashSet是不重复的,因此若插入元素已存在会直接返回false。若不存在元素,则会寻找存放元素的index。如果存在删除后的空位,则会将元素放到_freeList指向的空位上;如果不存在空位,则按_entries顺序插入元素。找到index后,即可将元素的HashCode及元素赋值到_entries[index]对应字段,当没有冲突时,Next值为-1;若存在冲突,则形成链表,将其添加到链表头,Next指向冲突的下一位置。

private bool AddIfNotPresent(T value, out int location){    bucket = ref GetBucketRef(hashCode); // bucket为ref int类型,若不存在冲突,bucket应为0,因为int默认值为0    // ...    int index;    if (_freeCount > 0) // 存在删除后的空位,将元素放在空位上    {        index = _freeList;        _freeCount--; // 更新删除后空位数量        _freeList = StartOfFreeList - entries[_freeList].Next; // 更新空位索引    }    else // 按_entries顺序存储元素    {        int count = _count;        if (count == entries.Length) // 容量不足,扩容        {            Resize();            bucket = ref GetBucketRef(hashCode);        }        index = count;        _count = count + 1;        entries = _entries;    }    {   // 赋值        ref Entry entry = ref entries![index];        entry.HashCode = hashCode;        entry.Next = bucket - 1; // 若不存在冲突则为-1,否则形成链表,指向冲突的下一元素索引        entry.Value = value;        bucket = index + 1; // 此处对bucket赋值,即改变_buckets对应元素,即将以插入元素哈希值为索引的_buckets值指向存放插入元素的_entries的索引+1        _version++;        location = index;    }    // ...    return true;}

插入时若列表容量不足,会调用Resize方法进行扩容。扩容后的大小为大于等于原大小2倍的最小素数。获取待扩容的大小后,以新大小重新分配entries内存,并调用Array.Copy方法将原内容拷贝到新位置。由于列表长度变了,因此哈希值会变,因此需要更新_buckets的内容(_entries索引),同理entry.Next的值也要更新。

private void Resize() => Resize(HashHelpers.ExpandPrime(_count), forceNewHashCodes: false);public static int ExpandPrime(int oldSize){    int num = 2 * oldSize;    if ((uint)num > 2146435069u && 2146435069 > oldSize)    {        return 2146435069;    }    return GetPrime(num); // 返回原大小2倍的最小素数}private void Resize(int newSize, bool forceNewHashCodes){    var entries = new Entry[newSize];    Array.Copy(_entries, entries, count);    // ...    _buckets = new int[newSize];    for (int i = 0; i < count; i++)    {        ref Entry entry = ref entries[i];        if (entry.Next >= -1) // 更新_buckets内容        {            ref int bucket = ref GetBucketRef(entry.HashCode); // 获取以新大小作为除数的哈希函数运算得到的哈希值            entry.Next = bucket - 1; // 更新Next            bucket = i + 1; // 更新_buckets的元素,指向重新计算的_entry索引+1        }    }    _entries = entries;}

当删除元素时,首先查找待删除元素是否存在。若哈希值存在冲突,会记录冲突链表的上一索引。查找到元素后,需要更新冲突链表的指针。删除元素后,会更新_freeCount空位数量,并将删除元素索引赋值给_freeList,记录删除空位,添加到空位链表头,其Next指向下一空位的相对位置。插入元素时,会将元素插入到_freeList记录的空位索引处,并根据该空位的Next更新_freeList的值。

public bool Remove(T item){    int last = -1;    int hashCode = item != null ? (_comparer?.GetHashCode(item) ?? item.GetHashCode()) : 0;    ref int bucket = ref GetBucketRef(hashCode);    int i = bucket - 1;    while (i >= 0)    {        ref Entry entry = ref entries[i];        if (entry.HashCode == hashCode && (_comparer?.Equals(entry.Value, item) ?? EqualityComparer<T>.Default.Equals(entry.Value, item)))        {            // 找到待删除元素            if (last < 0) // 待删除元素位于链表头部,更新_buckets元素值指向链表下一位置            {                bucket = entry.Next + 1;            }            else // 待删除元素非链表头,需更新链表上一元素Next值                entries[last].Next = entry.Next;            entry.Next = StartOfFreeList - _freeList; // 空位链表,记录下一空位索引相对位置,插入时根据该值更新_freeList            if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())                entry.Value = default!;            _freeList = i; // 记录删除元素位置,下次插入元素时,会插入在此            _freeCount++;  // 更新删除后空位数量            return true;        }        last = i; // 存在冲突,记录链表上一位置        i = entry.Next;    }    return false;}

到此,关于“.NET中的HashSet原理是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

.NET中的HashSet原理是什么

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

.NET中的HashSet原理是什么

这篇文章主要介绍“.NET中的HashSet原理是什么”,在日常操作中,相信很多人在.NET中的HashSet原理是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”.NET中的HashSet原理是什么”的疑
2023-06-29

.Net 6中WebApplicationBuilder原理和用法是什么

这篇文章将为大家详细讲解有关.Net 6中WebApplicationBuilder原理和用法是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。介绍.Net 6为我们带来的一种全新的引导程
2023-06-22

.NET Core剪裁器的工作原理是什么

这篇文章主要介绍“.NET Core剪裁器的工作原理是什么”,在日常操作中,相信很多人在.NET Core剪裁器的工作原理是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”.NET Core剪裁器的工作原理
2023-06-29

com和net域名的区别及原理是什么

com和net域名的区别及原理是什么,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。对于com域名和net域名来说,每一个域名都有自身的特点和优势,所以大家需要根据自己的需求,选
2023-06-06

.NET Framework无接触部署的工作原理是什么

这篇文章主要介绍.NET Framework无接触部署的工作原理是什么,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!.NET Framework在进行WEB应用程序的部署方面具有非常强大的优越性。我们可以通过本文介绍
2023-06-17

Android中Lint的原理是什么

这篇文章将为大家详细讲解有关Android中Lint的原理是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。Lint 的工作过程lint 工具的代码扫描工作流:应用源文件:源文件包含组成
2023-06-14

python中GIL的原理是什么

本篇文章给大家分享的是有关python中GIL的原理是什么,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Python主要用来做什么Python主要应用于:1、Web开发;2、数
2023-06-07

Android中Lifecycle的原理是什么

本文小编为大家详细介绍“Android中Lifecycle的原理是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“Android中Lifecycle的原理是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。
2023-06-29

java中IO的原理是什么

本篇文章为大家展示了java中IO的原理是什么,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、IO概念I/O 即输入Input/ 输出Output的缩写,其实就是计算机调度把各个存储中(包括内存和
2023-06-20

.NET中Entity Framework是什么

Entity Framework 是一个开发框架,用于从数据库中获取数据并管理数据对象。它是.NET平台的一部分,用于简化与关系型数据库的交互。Entity Framework 提供了一种对象关系映射 (ORM) 的方法,将数据库中的数据映
2023-09-22

NET中ExecuteScalar的用法是什么

在.NET中,ExecuteScalar是一个方法,用于执行查询并返回结果集中第一行的第一列的值。它通常用于执行返回单个值的查询,比如COUNT(*)或SUM(column)等聚合函数查询。使用ExecuteScalar方法的一般步骤如下:
2023-09-27

.net中HubbleDotNet的用法是什么

HubbleDotNet是一个在.NET平台上操作Hubble Telescope数据的库。它提供了一组类和方法,用于连接、查询和操作Hubble Telescope数据。HubbleDotNet的用法包括以下几个步骤:引用HubbleD
2023-10-25

Node中的net模块是什么

本文小编为大家详细介绍“Node中的net模块是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“Node中的net模块是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。1. OSI 七层协议模型想要学明白通
2023-07-05

kubernetes中的Scheduler原理是什么

本篇文章为大家展示了kubernetes中的Scheduler原理是什么,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一: 简介1.Kubernetes scheduler在整个系统中承担了“承上
2023-06-04

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录