皮皮网

【onenet android 源码】【源码溯源】【永中源码】哈希函数源码_哈希函数源码是什么

时间:2024-11-28 20:14:11 来源:linux kvm 源码分析 作者:易语言 vpn 源码

1.面试官:HashSet如何保证元素不重复?
2.rust基础学习--day17:HashMaps
3.Hermes源码分析(二)——解析字节码
4.Redis7.0源码阅读:哈希表扩容、哈希函数哈希函数缩容以及rehash
5.Chromium setTimeout/clearTimeout 源码分析
6.HashSet 如何保证元素不重复——hash码

哈希函数源码_哈希函数源码是源码源码什么

面试官:HashSet如何保证元素不重复?

       HashSet 实现了 Set 接口,由哈希表(实际是哈希函数哈希函数 HashMap)提供支持。HashSet 不保证集合的源码源码迭代顺序,但允许插入 null 值。哈希函数哈希函数这意味着它可以将集合中的源码源码onenet android 源码重复元素自动过滤掉,保证存储在 HashSet 中的哈希函数哈希函数元素都是唯一的。

       HashSet 基本操作方法有:add(添加)、源码源码remove(删除)、哈希函数哈希函数contains(判断某个元素是源码源码否存在)和 size(集合数量)。这些方法的哈希函数哈希函数性能都是固定操作时间,如果哈希函数是源码源码将元素分散在桶中的正确位置。HashSet 的哈希函数哈希函数基本使用方式如下:

       HashSet 不能保证插入元素的顺序和循环输出元素的顺序一致,实际上,源码源码HashSet 是哈希函数哈希函数无序的集合。具体代码示例如下:

       这表明,HashSet 的插入顺序为:深圳 -> 北京 -> 西安,而循环打印的顺序是:西安 -> 深圳 -> 北京。因此,HashSet 是无序的,不能保证插入和迭代的顺序一致。

       如果要保证插入顺序和迭代顺序一致,可以使用 LinkedHashSet 替换 HashSet。

       有人说 HashSet 只能保证基础数据类型不重复,却不能保证自定义对象不重复?其实不是这样的。使用 HashSet 存储基本数据类型,可以实现去重。将自定义对象存储到 HashSet 中时,HashSet 会依赖元素的 hashCode 和 equals 方法判断元素是否重复。如果两个对象的 hashCode 和 equals 返回 true,说明它们是相同的对象。例如,Long 类型元素之所以能实现去重,是因为 Long 类型中已经重写了 hashCode 和 equals 方法。

       为了使 HashSet 支持自定义对象去重,只需在自定义对象中重写 hashCode 和 equals 方法即可。这样,HashSet 就可以根据对象的 hashCode 和 equals 判断是否重复,从而实现自定义对象的去重。

       HashSet 保证元素不重复是源码溯源通过计算对象的 hashcode 值来判断对象的存储位置。当添加对象时,HashSet 首先计算对象的 hashcode 值,然后与其他对象的 hashcode 值进行比较。如果发现相同 hashcode 值的对象,HashSet 会调用对象的 equals() 方法来检查对象是否相同。如果相同,则不会让重复的对象加入到 HashSet 中,这样就保证了元素的不重复。具体实现源码基于 JDK 8,HashSet 的 add 方法实际调用了 HashMap 的 put 方法,而 put 方法又调用了 putVal 方法。在 putVal 方法中,首先根据 key 的 hashCode 返回值决定 Entry 的存储位置。如果有两个 key 的 hash 值相同,则会判断这两个元素 key 的 equals() 是否相同。如果相同,说明是重复键值对,HashSet 的 add 方法会返回 false,表示添加元素失败。如果 key 不重复,put 方法最终会返回 null,表示添加成功。

       总结而言,HashSet 底层是由 HashMap 实现的,它可以实现重复元素的去重功能。如果存储的是自定义对象,必须重写 hashCode 和 equals 方法。HashSet 通过在存储之前判断 key 的 hashCode 和 equals 来保证元素的不重复。

rust基础学习--day:HashMaps

       今天我们将继续深入 Rust 的基础学习,探索哈希映射(HashMap)这一强大的数据结构,与昨天学习的 String 类型相比,HashMap 将带给我们全新的体验。

       在前天的学习中,我们提到了 HashMap 是一种特殊的映射类型,相较于使用索引获取数据的方式,HashMap 通过键值(Key-Value)来访问数据,这种操作不仅效率更高,也更安全。让我们通过实例来直观理解 HashMap 的永中源码用法。

       首先,我们引入 HashMap 的使用,这是因为相对于其他内置类型如 Vector 和 String,HashMap 使用频率较低,因此并未被预加载。我们需要手动从 Rust 的标准库中引入它。

       创建 HashMap 的方式相对简洁,使用关联函数 `new` 来初始化,并指定键(`K`)和值(`V`)的类型。注意,HashMap 的所有项都必须具有相同的键和值类型。

       在插入键值对时,我们可以直接指定键和值,或者在后续的操作中插入,系统会自动检查类型的一致性。

       获取键值对中的值则更为直观,通过调用 `get` 方法并传入键的引用。在源码中,返回的是一个 Option 类型的引用,这可以确保我们能够安全地处理不存在的键。

       为了确保键的有效性,我们使用 `unwrap_or` 方法来处理可能的 `None` 值,这为我们提供了额外的保护措施,避免运行时错误。

       遍历 HashMap 时,我们同样使用引用来进行操作,类似于 Vector 的遍历方式,这使得我们可以高效地访问和操作映射中的元素。

       所有权问题是使用 HashMap 时需要考虑的关键点。在将数据放入 HashMap 后,原始数据的状态会受到影响。我们通过实例演示了不同类型(如标量类型、String 和 Vector)在所有权转移方面的差异,了解如何在安全地管理所有权的同时,有效利用 HashMap 的功能。

       在更新键值对时,我们可以根据不同的场景采取不同的策略。例如,如果键已存在,源码国度我们选择覆盖现有值;如果键已存在但希望忽略更改,我们可以选择使用默认值;如果需要根据旧数据进行更新,我们可以通过 `entry` 和 `or_insert` 方法来实现这一需求。

       HashMap 使用的哈希函数是 SipHash,它能够有效抵御 DDoS 攻击,确保数据的稳定性。尽管在速度方面可能不如其他哈希算法,但其安全性得到了保证。如果你对性能有更高要求,可以考虑使用 Rust 的 BuildHasher trait 自定义哈希函数,或者查找其他 Rust 社区中已有的实现。

       通过今天的学习,我们掌握了如何在 Rust 中使用 HashMap 进行键值映射的管理。无论是插入、读取还是更新操作,HashMap 都提供了一套简洁而高效的方法。希望这份学习能够帮助到你,记得为我们的分享点赞哦!

Hermes源码分析(二)——解析字节码

        前面一节 讲到字节码序列化为二进制是有固定的格式的,这里我们分析一下源码里面是怎么处理的

        这里可以看到首先写入的是魔数,他的值为

        对应的二进制见下图,注意是小端字节序

        第二项是字节码的版本,笔者的版本是,也即 上图中的4a

        第三项是源码的hash,这里采用的是SHA1算法,生成的哈希值是位,因此占用了个字节

        第四项是文件长度,这个字段是位的,也就是下图中的为0aa,转换成十进制就是,实际文件大小也是这么多

        后面的字段类似,就不一一分析了,头部所有字段的类型都可以在BytecodeFileHeader.h中看到,Hermes按照既定的内存布局把字段写入后再序列化,就得到了我们看到的字节码文件。

        这里写入的数据很多,以函数头的写入为例,我们调用了visitFunctionHeader方法,并通过byteCodeModule拿到函数的荣誉源码签名,将其写入函数表(存疑,在实际的文件中并没有看到这一部分)。注意这些数据必须按顺序写入,因为读出的时候也是按对应顺序来的。

        我们知道react-native 在加载字节码的时候需要调用hermes的prepareJavaScript方法, 那这个方法做了些什么事呢?

        这里做了两件事情:

        1. 判断是否是字节码,如果是则调用createBCProviderFromBuffer,否则调用createBCProviderFromSrc,我们这里只关注createBCProviderFromBuffer

        2.通过BCProviderFromBuffer的构造方法得到文件头和函数头的信息(populateFromBuffer方法),下面是这个方法的实现。

        BytecodeFileFields的populateFromBuffer方法也是一个模版方法,注意这里调用populateFromBuffer方法的是一个 ConstBytecodeFileFields对象,他代表的是不可变的字节码字段。

        细心的读者会发现这里也有visitFunctionHeaders方法, 这里主要为了复用visitBytecodeSegmentsInOrder的逻辑,把populator当作一个visitor来按顺序读取buffer的内容,并提前加载到BytecodeFileFields里面,以减少后面执行字节码时解析的时间。

        Hermes引擎在读取了字节码之后会通过解析BytecodeFileHeader这个结构体中的字段来获取一些关键信息,例如bundle是否是字节码格式,是否包含了函数,字节码的版本是否匹配等。注意这里我们只是解析了头部,没有解析整个字节码,后面执行字节码时才会解析剩余的部分。

        evaluatePreparedJavaScript这个方法,主要是调用了HermesRuntime的 runBytecode方法,这里hermesPrep时上一步解析头部时获取的BCProviderFromBuffer实例。

        runBytecode这个方法比较长,主要做了几件事情:

        这里说明一下,Domain是用于垃圾回收的运行时模块的代理, Domain被创建时是空的,并跟随着运行时模块进行传播, 在运行时模块的整个生命周期内都一直存在。在某个Domain下创建的所有函数都会保持着对这个Domain的强引用。当Domain被回收的时候,这个Domain下的所有函数都不能使用。

        未完待续。。。

Redis7.0源码阅读:哈希表扩容、缩容以及rehash

       当哈希值相同发生冲突时,Redis 使用链表法解决,将冲突的键值对通过链表连接,但随着数据量增加,冲突加剧,查找效率降低。负载因子衡量冲突程度,负载因子越大,冲突越严重。为优化性能,Redis 需适时扩容,将新增键值对放入新哈希桶,减少冲突。

       扩容发生在 setCommand 部分,其中 dictKeyIndex 获取键值对索引,判断是否需要扩容。_dictExpandIfNeeded 函数执行扩容逻辑,条件包括:不在 rehash 过程中,哈希表初始大小为0时需扩容,或负载因子大于1且允许扩容或负载因子超过阈值。

       扩容大小依据当前键值对数量计算,如哈希表长度为4,实际有9个键值对,扩容至(最小的2的n次幂大于9)。子进程存在时,dict_can_resize 为0,反之为1。fork 子进程用于写时复制,确保持久化操作的稳定性。

       哈希表缩容由 tryResizeHashTables 判断负载因子是否小于0.1,条件满足则重新调整大小。此操作在数据库定时检查,且无子进程时执行。

       rehash 是为解决链式哈希效率问题,通过增加哈希桶数量分散存储,减少冲突。dictRehash 函数完成这一任务,移动键值对至新哈希表,使用位运算优化哈希计算。渐进式 rehash 通过分步操作,减少响应时间,适应不同负载情况。定时任务检测服务器空闲时,进行大步挪动哈希桶。

       在 rehash 过程中,数据查询首先在原始哈希表进行,若未找到,则在新哈希表中查找。rehash 完成后,哈希表结构调整,原始表指向新表,新表内容返回原始表,实现 rehash 结果的整合。

       综上所述,Redis 通过哈希表的扩容、缩容以及 rehash 动态调整哈希桶大小,优化查找效率,确保数据存储与检索的高效性。这不仅提高了 Redis 的性能,也为复杂数据存储与管理提供了有力支持。

Chromium setTimeout/clearTimeout 源码分析

       Chromium版本.0..3中setTimeout函数的工作流程涉及大量源码,包括线程、消息循环、任务队列和操作系统定时器函数。本文仅分析setTimeout的关键步骤。

       setTimeout函数通过创建包含回调函数和延时时间的action对象,调用DOMTimer::Install进行处理。DOMTimer::Install通过DOMTimerCoordinator::InstallNewTimeout向定时器哈希表timers_插入一个定时器对象,生成唯一timeout_id。

       timeout_id由NextID生成,每次调用setTimeout返回递增的值,用于唯一标识每个定时器任务。timers_是一个哈希表,存放定时器对象,与任务一一对应。

       创建定时器对象时,通过定时器的延时时间获取任务类型,并将回调函数与任务类型关联,最终通过web_task_runner_获取相应的任务运行器,并在TimerBase::SetNextFireTime调用web_task_runner_->PostDelayedTask提交延迟任务。

       PostDelayedTask将延迟任务插入到延迟任务队列中,并更新当前线程的唤醒时间。延迟任务队列是优先队列,用于管理按延时时间排序的任务。

       通过GetNextScheduledWakeUpImpl获取优先队列的队头任务,创建唤醒任务用于在线程唤醒时执行延迟任务。唤醒任务只包含延时时间,不包含回调函数。

       UpdateDelayedWakeUpImpl根据新创建的唤醒任务更新唤醒任务队列。如果延迟任务队列中的任务延时时间较短,新任务可能无法立即进入唤醒任务队列。

       调用操作系统定时器函数,如在Mac下调用CFRunLoopTimerSetNextFireDate,在Windows下调用SetTimer,在Android下调用timerfd_settime,在指定延时后唤醒线程。

       线程睡眠后,唤醒线程执行已到期的延迟任务,将到期任务从延迟任务队列移出并加入工作队列。ThreadControllerWithMessagePumpImpl::DoWorkImpl找到并执行工作队列中的任务。

       面试题:setTimeout延迟时间不准确的原因可能有:硬件层面的时间不准确、操作系统不保证定时器函数的精确性、CPU处理大量定时任务时可能出现部分任务延迟执行。

       clearTimeout与clearInterval功能相同,DOMTimer::RemoveByID从timers_哈希表中移除指定timeout_id对应的定时器对象,将回调函数置空,视为任务取消。

HashSet 如何保证元素不重复——hash码

       HashSet确保元素不重复,主要通过add方法实现,该方法会检查是否存在元素,若存在则不添加,否则添加。HashSet基于HashMap实现,HashMap的key对应HashSet的元素。HashSet借助哈希函数确保元素的唯一性。查看add方法时,可以发现其调用的是HashMap的put方法。

       put方法在调用putVal方法后执行,该方法中的hash函数用于计算哈希值,并通过与右移位后的异或运算,以使哈希分布更均匀。

       进一步深入,putVal方法中注释显示,如果返回的值存在,说明元素已存在。通过分析源码,可以得出HashSet依赖哈希算法的唯一性,确保每个元素的哈希值是独一无二的。

       在HashSet中,equals方法默认调用Object的equals方法,比较的是内存地址。但在实际使用中,通常会使用String或Integer等封装类型,这些类型会重写equals方法。初学者可能会对此感到困惑,但理解这一点对于处理HashSet中的元素比较非常重要。

PostgreSQL-源码学习笔记(5)-索引

       索引是数据库中的关键结构,它加速了查询速度,尽管会增加内存和维护成本,但效益通常显著。在PG中,索引类型丰富多样,包括B-Tree、Hash、GIST、SP-GIST、GIN和BGIN。所有索引本质上都是独立的数据结构,与数据表并存。

       查询时,没有索引会导致全表扫描,效率低下。创建索引可以快速定位满足条件的元组,显著提升查询性能。PG中的索引操作函数,如pg_am中的注册,为上层模块提供了一致的接口,这些函数封装在IndexAmRoutine和IndexScanDesc中。

       B-Tree索引采用Lehman和Yao的算法,每个非根节点有兄弟指针,页面包含"high key",用于快速扫描。PG的B-Tree构建和维护流程涉及BTBuildState、spool、元页信息等结构,包括创建、插入、扫描等操作。

       哈希索引在硬盘上实现,支持故障恢复。它的页面结构复杂,包括元页、桶页、溢出页和位图页。插入和扫描索引元组时,需要动态管理元页缓存以提高效率。

       GiST和GIN索引提供了更大的灵活性,支持用户自定义索引方法。GiST适用于通用搜索,而GIN专为复合值索引设计,支持全文搜索。它们在创建时需要实现特定的访问方法和函数。

       尽管索引维护有成本,但总体上,它们对提高查询速度的价值不可忽视。了解并有效利用索引是数据库优化的重要环节。

HashMap实现原理

        HashMap在实际开发中用到的频率非常高,面试中也是热点。所以决定写一篇文章进行分析,希望对想看源码的人起到一些帮助,看之前需要对链表比较熟悉。

        以下都是我自己的理解,欢迎讨论,写的不好轻喷。

        HashMap中的数据结构为散列表,又名哈希表。在这里我会对散列表进行一个简单的介绍,在此之前我们需要先回顾一下 数组、链表的优缺点。

        数组和链表的优缺点取决于他们各自在内存中存储的模式,也就是直接使用顺序存储或链式存储导致的。无论是数组还是链表,都有明显的缺点。而在实际业务中,我们想要的往往是寻址、删除、插入性能都很好的数据结构,散列表就是这样一种结构,它巧妙的结合了数组与链表的优点,并将其缺点弱化(并不是完全消除)

        散列表的做法是将key映射到数组的某个下标,存取的时候通过key获取到下标(index)然后通过下标直接存取。速度极快,而将key映射到下标需要使用散列函数,又名哈希函数。说到哈希函数可能有人已经想到了,如何将key映射到数组的下标。

        图中计算下标使用到了以下两个函数:

        值得注意的是,下标并不是通过hash函数直接得到的,计算下标还要对hash值做index()处理。

        Ps:在散列表中,数组的格子叫做桶,下标叫做桶号,桶可以包含一个key-value对,为了方便理解,后文不会使用这两个名词。

        以下是哈希碰撞相关的说明:

        以下是下标冲突相关的说明:

        很多人认为哈希值的碰撞和下标冲突是同一个东西,其实不是的,它们的正确关系是这样的,hashCode发生碰撞,则下标一定冲突;而下标冲突,hashCode并不一定碰撞

        上文提到,在jdk1.8以前HashMap的实现是散列表 = 数组 + 链表,但是到目前为止我们还没有看到链表起到的作用。事实上,HashMap引入链表的用意就是解决下标冲突。

        下图是引入链表后的散列表:

        如上图所示,左边的竖条,是一个大小为的数组,其中存储的是链表的头结点,我们知道,拥有链表的头结点即可访问整个链表,所以认为这个数组中的每个下标都存储着一个链表。其具体做法是,如果发现下标冲突,则后插入的节点以链表的形式追加到前一个节点的后面。

        这种使用链表解决冲突的方法叫做:拉链法(又叫链地址法)。HashMap使用的就是拉链法,拉链法是冲突发生以后的解决方案。

        Q:有了拉链法,就不用担心发生冲突吗?

        A:并不是!由于冲突的节点会不停的在链表上追加,大量的冲突会导致单个链表过长,使查询性能降低。所以一个好的散列表的实现应该从源头上减少冲突发生的可能性,冲突发生的概率和哈希函数返回值的均匀程度有直接关系,得到的哈希值越均匀,冲突发生的可能性越小。为了使哈希值更均匀,HashMap内部单独实现了hash()方法。

        以上是散列表的存储结构,但是在被运用到HashMap中时还有其他需要注意的地方,这里会详细说明。

        现在我们清楚了散列表的存储结构,细心的人应该已经发现了一个问题:Java中数组的长度是固定的,无论哈希函数是否均匀,随着插入到散列表中数据的增多,在数组长度不变的情况下,链表的长度会不断增加。这会导致链表查询性能不佳的缺点出现在散列表上,从而使散列表失去原本的意义。为了解决这个问题,HashMap引入了扩容与负载因子。

        以下是和扩容相关的一些概念和解释:

        Ps:扩容要重新计算下标,扩容要重新计算下标,扩容要重新计算下标,因为下标的计算和数组长度有关,长度改变,下标也应当重新计算。

        在1.8及其以上的jdk版本中,HashMap又引入了红黑树。

        红黑树的引入被用于替换链表,上文说到,如果冲突过多,会导致链表过长,降低查询性能,均匀的hash函数能有效的缓解冲突过多,但是并不能完全避免。所以HashMap加入了另一种解决方案,在往链表后追加节点时,如果发现链表长度达到8,就会将链表转为红黑树,以此提升查询的性能。

关键词:靓号论坛源码

copyright © 2016 powered by 皮皮网   sitemap