数据结构(邓俊辉)学习笔记】词典 02—— 散列函数
文章目录
- 1. 冲突难免
- 2. 何为优劣
- 3. 整除留余
- 4. 以禅为师
- 5. M + A +D
- 6. 平方取中
- 7. 折叠汇总
- 8. 伪随机数
- 9. 多项式
- 10. Vorldmort
1. 冲突难免
好,接下来的这一节我们就来介绍散列策略中的第一项,也是最重要的技术,散列函数的设计与定制。
在上一节我们已经看到,散列策略具有诸多的优势和巨大的潜力。然而,尽管如此,冲突却是这一策略的致命缺陷。然而,更糟糕的是,从理论上讲,这一缺陷是无法根本消除的。
依然回到我们电话键盘的实例,借助字符来助记电话号码的策略尽管十分精巧,但同样不难举出冲突的实例。比如有时你需要找某个人,但对应的号码却有可能属于某个学院。你要找的或许是欧几里得,但这个电话却有可能属于小黄鸭。你可能需要联系太阳鸟,但接听这个电话的却可能是只臭虫,凡此种种,皆是冲突。
~
我们知道散列函数无非是一个映射,其功能无非是将词条空间中的元素映射到散列表地址空间。 而在通常的情况下,前者要远远地大于后者,因此绝不可能是一个单射。
当然,面对这种现实,我们并非无可作为。这方面的一条好消息是,在一般的实际应用中,我们还是可以在一定的程度上得到一个近似的单射。
比如黑白打印机经常要做的一件事就是,将原本是彩色的图像转换为黑白灰度的。从映射的角度来看,这样一种转换也是散列,对于这个散例而言,数据项的取值范围应该是图像中各像素可能取的颜色种类数,也就是2的24次方,而散列表的长度也就是灰度的级别2的8次方。二者之间的差异也高达10的4至5次方。
然而,尽管经过了如此之大幅度的压缩,对我们辨识灰度图像中的物体并没有多大的影响。当然,这主要得益于图像中各像素在空间上是有一个分布的。然而,即便离开这个条件,一般意义上的散列也是有可能做得足够实用的。为此我们需要做两件事,这也是实现散列的两项基本任务。
- 首先我们需要精心的设计散列表及其对应的散列函数,消除掉一些能够导致冲突的先天性因素,从而尽可能地压低日后发生冲突的概率。这也是本节讨论的主题。
- 当然,既然无论如何都不可能从根本上消除冲突。所以我们也应该在事先准备好充分的预案,日后一旦发生冲突,我们就可到此预案及时予以排解。关于冲突的排解技巧,往下看。
2. 何为优劣
什么样的散列函数更好?好的散列函数又必须具备哪些要素呢?
- 首先它必须具有确定性,也就是说词条空间中的任何一个元素都应该能被唯一的映射到散列地址空间中的某一元素。这种映射关系必须是明确的。
- 其次,从计算的角度来看,这种映射应该能够快速地兑现。
- 第三条,相对于更大的取值空间,散列的地址空间要小得多,也需要更加充分的加以利用。为此,散列函数必须是一个满射。
- 同样是为了充分地利用有限的散列空间并降低冲突发生的概率,我们应该使得各关键码被映射到散列表各位置的概率尽可能地接近。也就是要将所有可能的关键码,尽可能均匀地压缩到散列空间中,最大程度的避免很多元素在局部汇聚的现象,也就是所谓的 clustering。
以下我们就按照这4条基本的标准介绍若干种常用的散列函数,并分析起各自的优劣以及适用场合。
3. 整除留余
我们所设计的第一个散列函数,使用的是所谓的除余法。实际上我们对这种方法应该并不陌生。是的,在此前清华大学校园电话簿那个应用实例中,我们所采用的正是这种散列方法。当然,这里有一个问题,你应该还记得当时的表长,也就是这里取模余的基底,是取做90001,为什么取这样一个有点古怪的数字呢?
是的,这个数字的确有些古怪。因为照常理,我们或许会选用一个更为规整的数字,比如熟知二进制的你,很有可能会选取2的若干次方。你甚至会发现,如此可以更加高效地来计算散列地址。
因为此时 m 的二进制展开应该是 1 后面接 1 个 2 个 3 个直到第 k 个 0。而此时,相对于 M 的模余计算,也就等同于相对于 M-1 的位与运算,是的,就计算效率而言,的确如此。
然而就我们刚才所确立的第四条原则,也就是均匀性而言,这种方法却是非常糟糕的。因为在整个关键码的取值空间中存在某个特定的子集,该子集中的每一个元素都会统一地映射到散列表中的某一个特定单元,而不是均匀地分布于整个散列表中。
实际上,M 的每一个同余类,都是这样的一个实例。放弃这些形式极其规整的表长,映射的均匀性的确会有所改进,但这还不是根本的办法。
一种最为简明的策略,莫过于将表长取作为素数。 此时,不仅数据对于散列表的覆盖程度能够达到最充分,而且在散列表中的分布,也将达到最均匀。我不凡从一个角度来理解这一点。
4. 以禅为师
知道在实际应用中,我们所处理的数据通常都具有一定的局部性。其中一种典型的现象是数据序列中的数据项大多是按某一步长单调变换。想想你在程序中常写的 while 或者 for 之类的循环,就应该不难理解这一点。
如果数据序列的步长为 S,我们来考察 S 与 M 的最大公因子,将其记作 G。
假设这就是散列表的地址空间,其长度为 M,而你的数据序列呢?将从某一个位置开始以 S 为间隔,逐次的转入下一项以及再下一项。当然,如果你的数据序列足够长,它就有可能会从另一端回到这个空间,并且继续以 S
为间隔在散列地址空间中逐次访问下去。
现在考察这个数据序列在散列地址空间中留下的足迹。这些足迹能够遍历整个散列表空间吗?如果能,那么这种散列方法就具有均匀性,反之就不具有。
借助数论的知识,不难知道数据序列的足迹能够遍布整个散列表,当且仅当刚才这个最大公因子 G 等于1。
请注意,因为可能有不同的程序,而每一程序的每一次运行所对应的这个步长未必相等。也就是说这里的 M 相对于几乎任何 S 最大公因子都只能是 1。这意味着什么呢?意味着 M 应该是一个素数。
很有意思是很动物包括一些昆虫都懂得这个道理。在这里我们再次向禅学习,学习它的哲学。没错,禅的哲学。
昆虫学告诉我们禅有很多变种,每一个变种都有其固定的生命周期。比如有些蚕是3年,而有些却是17年。那么蝉是否有某一个子类寿命是14年、15年甚至16年呢?据我所知没有。为什么没有呢?不妨回到我们的散列表。实际上,每一只禅在生命周期都可以对应为一个散列表,蝉的寿命有多长,散列表也就有多长。
~
所以有些种类的蝉所对应的散列表长度为13,有些等于17,当然也可能是11等等这类的数字。我们知道,在自然界蝉是弱势群体,它有很多天敌,无论是螳螂还是螳螂之后的那只黄雀。每一种天敌大致也有一个自己的生命周期,这就相当于我们这里的步长 S。每经过 S 年,蝉的天敌都会更新一代。当然蝉不能去改变弱肉强食的法则,但唯一能期望的只能是在同一年不要遇到更多的天敌。
~
相应的,反过来所有的天敌都应该尽可能在每一年分布得更加均匀。这更有利于蝉作为一个物种,得以在自然界延续下去。用数学的语言来说,如果蝉能够选择自己的生命周期,那么自然的就应该选择与天敌的生命周期保持最大公约数为1。
而为了以更多的天敌在生命周期上保持这种关系,尽管禅有不同的变种,但是在经过长时间的进化之后,每一个变种都会聪明地将其生命周期设定为素数,就像我们所看到的那样,取做17、13等等。
5. M + A +D
我们接下来将要介绍的是 MAD法,名字听出来多少有点恐怖,这种方法可以认为是除余法的改进或推广。
是的,如果更为严格地考察均匀性,除余法的确还存在一定的缺陷,体现在两个方面问题。
- 问题首先出在零点,无论表长 M 取值如何,零点总是会被映射到零点,也就是说存在不动点,这与任何元素都拥有均等的概率被映射到任何位置的原则是完全相悖的。
- 其次,尽管其他的元素都大体拥有均等的概率,被分配到各个桶中,但不同关键码之间的这种映射却存在着某种简明的关联关系。具体来说,经过散列映射之后,相邻的关键码也必然依然的相邻。
就此意义而言,除余法所拥有的均匀性只是低阶。当然,我们更希望实现更高阶的均匀性,比如至少临近的关键码在经过散列之后,不要继续地彼此临近。那么如何实现这种更高阶的均匀性呢?
为此我们需要对除余法略做改进。除了表长继续取做素数,我们还需要另外两个整数,整个散列的计算过程包括三步,首先做一次乘法,再做一次加法,最后再做整除模余。这里的计算步骤增加了,但如此却可以针对性地修复此前的两个缺陷。
你看出来了吗?是的,新引入的整数 b 可以视作是偏移量,如此即可有效的消除不动点。而另一个引入的整数 a
则扮演着步长的角色,也就是说,在经过散例变换之后,原本相邻的关键码将变成间隔为 a,从而不再继续相邻。
当然,实际应用的需求多种多样的,我们这里暂且只考虑最普遍的应用。实际上在不同的场合,散列的原则都有可能发生变化,甚至翻转,比如在某些特殊的场合,未必需要高阶的乃至通常的均匀性,比如在一些几何计算的场合,我们需要处理的往往是来自于高维空间中的一系列点。为了将他们压缩到更加低维的空间,我们往往也需要借助散列。此时我们对散列的要求可能恰恰相反,也就是要尽可能使得临近的关键码被映射到临近的位置,这也就是所谓的 locality—Sensitive hashing, 散列技术在当今的信息处理中之所以能够无处不在,恰恰在于它的这些准则是灵活的。
再比如在我们这个课程中所讨论的主要技术多是指在将一个相对而言更大的空间通过散列映射压缩至一个相对而言更小的空间。而实际上反过来也是大有用处的,这也就是所谓的密码学。
6. 平方取中
散列函数是一个庞大的家族,其中的成员形形色色,各有所长,这也是散列技术的趣味性和魅力所在。
接下来我们不妨就来看看其中的几个典型代表。
- 首先是所谓的数字分析法,这里的原则是无论此前的关键码有多少位,我们只从其中挑选出若干位构成散列地址。
比如我们可以将关键码的数位间隔的分为两组,并选择其一组成最终的散列地址。当然,尽管这里需要分组,但分组的方式必须是事先确定的,否则也就违背了确定性的原则。
~
另外,这种方法的缺陷也十分明显,因为没有被选用的那一组数位,对最终的散列地址没有任何的影响和贡献,没有足够的体现均匀性的要求。
就此可以有很多种改进方法。
- 比如平方取中法。按照这种策略我们首先要计算出关键码的平方,并截取中间的若干数位作为散列地址。以123为例,如果没有算错,它的平方应该是15129。因此我们可以取居中的三位,也就是512作为对应的散列地址。更复杂的情况也是按同样的方法处理。至此,你或许会有一个疑问,这里为什么我们会倾向于保留居中的数位呢?实际上,这正是为了使得构成原关键码的各数位能够对最终的散列地址有尽可能接近的影响。
来看这样一幅图,它将一个数的平方运算分解为一系列的左移操作以及若干次加法。如果最终的结果是这个,从图中不难看出,如果忽略进位,每一个数位都是由原关键码中的若干数位经求和得到的。因此两侧的数位是由更少的原数位累计而得。而越是居中的数位,则是由更多的原数位累积而得。因此截取居中的若干位,可以使得原关键码的各数位对最终地址的影响彼此更为接近。
7. 折叠汇总
为了使得原关键码中的各数位对最终地址拥有更为平均的影响力,从而实现更好的均匀性,还有一些非常有趣的散列函数值得借鉴。
- 比如所谓的折叠法,可以按照最终散列地址的宽度将原关键码中的个数位依次分为若干组,并将每一组独立的视作为一个整数,而是他们的总和就是对应的散列码。
- 这种方法还有一些有趣的变种,比如原关键码中的各数位将按照交替的方向转换为对应的整数,123 456 789,诸如此类。
形象地说,这个好比我们需要做一张千层饼。为此我们首先需要将和好的面擀成一张薄片,前一方法会将面继续地切成均等的一段,并按次序将他们落起来,最后通过挤压将他们合成一张薄饼。而相对于真实的千层品制作工艺后一种方法更为接近,也就是以一种往复折返的方式,同样将面折叠起来,并最终压实。
尽管同样是经过分段和对齐,最终压实的那一步,却未必只能通过求和。比如对于二进制,很多种位运算都具有同样的功效,比如最常用的就是按位做异或。就不妨就此来验证这里所给的实例。
当然,此类方法零零种,就此,你应该对反例函数的特征有了更具体的理解。是的,总之大致说来,散列函数越是随机,越是没有规律就越好。
8. 伪随机数
谈到随机,你应该很自然会想到系统所提供的随机数发生器。
比如这就是一种实现的方式,可以看到这里每一个所谓的随机数,实际上都是在前一个所谓随机数基础上,按照确定的计算规则递推而得。因此更为准确的应该称之为伪随机数发生器。
就逻辑效果而言,这等同于将取值范围以内的所有整数按照这种规则重新编排为一个貌似随机实则确定的序列。而这个发生器所有返回的只不过是在这个序列中对应于某个特定秩的那个元素。比如一种最常见的方法就是将这个秩取作系统当前的时间。
如果就接口参数的形式,对散列函数以及伪随机数发生器函数做一对比,我们就会发现二者惊人的相似。 难道不是吗?只不过前者是经统一散列转换之后所得的关键码,而后者只是尾随基数序列中的某个值。 因此我们不妨直接借助后者来实现前者。事实上很有意思的是,如果反过来考察,此前我们已经确立的那四条准则,无论是确定性、高效性、满射性还是均匀性,它们恰好同时也是评判随机数发生器的重要标准。
既然每一个伪随机数发生器都可视作为一个散列函数,我们也可以将散列函数的设计难题转交给伪随机数发生器的设计者,我们可以直接套用他们的工作成果。当然,事情还不是这么简单。
如果采用伪随机数法,有一点是非常重要的。事实上,在不同的平台和环境中所提供的伪随机数发生器所采用的算法不尽相同。即便在同一个平台环境中,不同的历史版本也可能对应于不同的随机数发生算法。 因此你在特定时间、特定平台上所生成的散列表未必可以直接移植到其他的平台。对这一点你应该保持足够的谨慎。
9. 多项式
当然,在实际应用中,原始数据的关键码未必天生都是整数形式,因此往往需要先做一个预处理,将其转化为整数,称作散列码 hashcode,然后才可以将其进一步的转化为桶数组中的地址,浮点数以及字符的 hashcode 转换并不困难。
因此接下来我们重点讨论一下字符串型关键码应该如何更好地完成这种转换。比如一种行之有效的方法就是所谓的多项式法。
比如这就是一个长度为 n 的字符串。
- 我们首先将其中的每一个字符分别转换为对应的整数。
- 接下来,再将这 n 个整数,分别视作为一个 n 次多项式的 n 个系数,并采用事先确定的某一个常数 a 计算出这个多项式的具体数值,并将其作为散列地址。
请注意这样一个一元 n 次多项式可以在 O(n) 而不是 n 平方的时间内计算得出。因为时间关系,我们忽略掉具体的算法。如果你这句算法还不甚了解,这个插图应该会对你有所帮助。当然这里的 O(n)毕竟涉及较为复杂的乘法运算,能否加以避免呢?答案也是肯定的。
比如这就是一种可行而有效的方法。实验数据也表明,这种散列码转换算法非常适用于英文字符串。可以看到,这里也是通过一个 for 循环依次的处理串中的各个字符。对于每一个字符,我们首先将其转换为整数,并对其做累计,而在每次这样的累计之前,原有的累计值都要按这种方式做一个数位变换。
这图可以帮助我们理解整个变换的过程。如果这是此前累计值的二进制展开,一般的取做32位,那么调整的实际效果就是将前端的5个比特与此后的27个比特互换位置。这一不断调整、不断累加的过程,实际上可以视作为是对以上多项式计算的近似,只不过这里消除了相对费时的乘法计算。
至于如何来具体理解和解释这种近似的效果。当然,无论是原始的多项式法,还是变通之后的近似方法,其计算过程都不明显的相对复杂。
你或许会质疑,有这个必要吗?答案是的确有。
10. Vorldmort
针对字符串关键码的hashcode转化,最自然的方法莫过于此。具体的与多项式法一样,这里也将每一个字符事先与某一个数值对应起来。而所有字符所对应数值的总和也自然就是所对应的 hashcode。
这个方法与此前所介绍的折叠法类似。但这里的情况却有所不同,以至于这种方法将会导致频繁地冲突。
来看这样一个字符串—— Tom Marvel Riddle。
如果你熟悉 Harry Potter 的故事,那么对 Tom Marvel Riddle 就不会陌生。是的,它是伏地魔的一个化身,而这个名字也是伏地魔的一个化名。
你也应该会记得这样一句话——I am Load voldemort。
是的,这是 Tom Marvel 在自认为已经掌控了Harry Potter 的生死时,颇为得意地写出的一句话。而谜底恰好就藏在这两个字符串中。即便你不知道这个故事,现在也不难看出这两个字符串实际上是由同一组字符构成的,只不过排列次序不同而已。
如果按照我们直观的想法,将该字符串中的所有字符所对应的整数累积起来,就会得到一个 hashcode 196。那么 I am Load voldemort,不难理解,尽管次序有所调整,但因为加法满足交换率,所以同样应该得到196。
当然,既然这两个串都是由同一组字符构成的,所以 hashcode 相同也毫不奇怪。然而事实是,即便是由不同的一组数字所构成的英文字符串,按照这种映射方法也同样会有很高的概率发生冲突。
继续刚才的故事,应该还记得第七集。是的,在那集中我们终于得知 Harry Potter 居然也是 voldemort 化身或者死亡魂器之一。那么在冥冥之中,Harry Potter 是否真的与伏地魔有某种因果联系呢?我们不妨来做一次散列,如果还不是占卜的话,来看这样字符串。
既然 Tom Marvel与 voldemort 的联系是源自其名字的巧合,那么 Harry Potter 与 voldemort也应该是如此。嗯,必须的。
相关文章:

数据结构(邓俊辉)学习笔记】词典 02—— 散列函数
文章目录 1. 冲突难免2. 何为优劣3. 整除留余4. 以禅为师5. M A D6. 平方取中7. 折叠汇总8. 伪随机数9. 多项式10. Vorldmort 1. 冲突难免 好,接下来的这一节我们就来介绍散列策略中的第一项,也是最重要的技术,散列函数的设计与定制。 在上…...

Python学习(1):使用Python的Dask库实现并行计算
目录 一、Dask介绍 二、使用说明 安装 三、测试 1、单个文件中实现功能 2、运行多个可执行文件 最近在写并行计算相关部分,用到了python的Dask库。 Dask官网:Dask | Scale the Python tools you love 一、Dask介绍 Dask是一个灵活的并行和分布式…...

数据结构 - 哈希表
文章目录 前言一、哈希思想二、哈希表概念三、哈希函数1、哈希函数设计原则2、常用的哈希函数 四、哈希冲突1、什么是哈希冲突2、解决哈希冲突闭散列开散列 五、哈希表的性能分析时间复杂度分析空间复杂度分析 前言 一、哈希思想 哈希思想(Hashing)是计…...

电商选品这几点没做好,等于放弃80%的流量!
在竞争激烈的电商领域,选品是决定店铺命运的核心环节。到底是哪些关键要点能够帮助我们在选品时抢占流量高地,稳步出单呢? 一、深入了解市场需求 选品的第一步是对市场进行深入调研。要关注当前的消费趋势、热门品类以及潜在的需求缺口。通…...
【教程】最新可用!Docker国内镜像源列表
转载请注明出处:小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你,欢迎[点赞、收藏、关注]哦~ 目录 镜像加速器地址 用法示例 一、自动配置地址 二、配置单次地址 镜像加速器地址 Docker镜像加速站https://hub.uuuadc.top/docker.1panel.live…...
使用RabbitMQ在Spring Boot入门实现简单的消息的发送与接收
文章目录 要引入spring-boot-starter-amqp依赖才能开始后续操作 1. 配置RabbitMQ地址2. 编写消息发送测试类3. 实现消息接收 在本文中,我们将介绍如何在Spring Boot应用中使用RabbitMQ实现消息的发送与接收。我们将创建两个服务,一个用于发送消息&#x…...
基于物联网的水质监测系统设计与实现:React前端、Node.js后端与TCP/IP协议的云平台集成(代码示例)
一、项目概述 随着环境保护意识的增强,水质监测在水资源管理和污染防治中变得尤为重要。本项目旨在设计一个基于物联网的水质监测系统,能够实时监测水中的pH值、溶解氧、电导率和浊度等参数,并将数据传输至云端,以便进行分析和可…...
Vcpkg安装指定版本包或自定义安装包
在使用 vcpkg 安装特定版本的包或自定义包时,你可以按照以下步骤进行操作: 安装特定版本的包 列出可用的版本: 使用以下命令列出特定包的所有可用版本: vcpkg search <package-name>安装特定版本: 使用 vcpkg …...

【C++深度探索】红黑树实现Set与Map的封装
🔥 个人主页:大耳朵土土垚 🔥 所属专栏:C从入门至进阶 这里将会不定期更新有关C/C的内容,欢迎大家点赞,收藏,评论🥳🥳🎉🎉🎉 文章目录…...

终于有人把客户成功讲明白了
作者:沈建明 对ToB企业来说,只有客户成功才能带来持久增长,在SaaS企业下行大背景下,客户成功是唯一的救命稻草。大家是不是都听过这样的说法? ToB和SaaS企业的老客户贡献对于企业至关重要。因为获取新客户的成本是留…...

[新械专栏] 肾动脉射频消融仪及一次性使用网状肾动脉射频消融导管获批上市
近日,国家药品监督管理局批准了上海魅丽纬叶医疗科技有限公司“肾动脉射频消融仪”和“一次性使用网状肾动脉射频消融导管”两个创新产品注册申请。 肾动脉射频消融仪由主机、脚踏开关、主机连接线、中性电极连接线以及电源线组成。一次性使用网状肾动脉射频消融导…...

leetcode-119-杨辉三角II
原理: 1、初始化每行一维数组nums[1]; 2、从第2行开始,在nums的头插入0(因为杨辉三角每行的第一个1相当于是上一行的1与其前面的0相加之和)后进行相加操作。 代码:...

【第八节】python正则表达式
目录 一、python中的re模块 1.1 基本匹配和搜索 1.2 替换和分割 1.3 编译正则表达式 二、正则表达式对象 2.1 re.RegexObject 和 re.MatchObject 2.2 正则表达式修饰符 - 可选标志 2.3 正则表达式模式 2.4 正则表达式实例 一、python中的re模块 正则表达式是一种独特的…...

三大浏览器Google Chrome、Edge、Firefox内存占用对比
问题 Chrome、Edg、Firefox三家究竟谁的占用少 结论 打开一个页面内存占用 Firefox>Edge>Chrome 打开打量页面内存占用 Firefox>Chrome>Edge 从监视器可以看到Edge增加一个页面增加一个页面不到100M而其它浏览器需要150M左右;Firefox浏览器主线程内存占用800M比…...

【wiki知识库】08.添加用户登录功能--后端SpringBoot部分
目录 一、今日目标 二、SpringBoot后端实现 2.1 新增UserLoginParam 2.2 修改UserController 2.3 UserServiceImpl代码 2.4 创建用户上下文工具类 2.5 通过token校验用户(重要) 2.6 创建WebMvcConfig 2.7 用户权限校验拦截器 一、今日目标 上篇…...
vue中nextTick的作用
nextTick是Vue.js提供的一个非常有用的方法,其主要作用是在DOM更新之后执行延迟回调函数。以下是nextTick的具体作用及其实现原理的详细解析: nextTick的作用 确保DOM更新完成: 当Vue实例的数据发生变化时,Vue会异步地更新DOM。…...

计算机网络面试-核心概念-问题理解
目录 1.计算机网络OSI协议七层结构功能分别是什么?如何理解这些功能 2.物理层、数据链路层、网络层、传输层和应用层,这五个层之间功能的关系,或者说是否存在协调关系 3. 数据链路层功能理解 4.MAC地址和以太网协议 5.以太网协议中的CSMA…...
go语言创建协程
前言 Go 语言中,协程是通过 go 关键字来创建的,这使得 Go 语言成为实现并发程序的一个非常直观和强大的工具。Go 运行时管理着协程,这些协程在内部被称为 goroutine。 协程(goroutines)本身是轻量级的线程,…...
RabbitMQ之基于注解声明队列交换机:使用@RabbitListener实现消息监听
文章目录 什么是RabbitListener?队列和交换机的基本概念使用RabbitListener注解声明队列和交换机代码解析1. QueueBinding2. 消费者方法 运行原理应用场景总结 在现代的微服务架构中,消息队列是一种重要的异步通信机制。RabbitMQ作为一种流行的消息代理软…...
【grafana 】mac端grafana配置的文件 grafana.ini 及login
brew services start grafana 以后,怎么知道mac端的配置文件的路径 brew services restart grafana#brew services start grafana在macOS上使用Homebrew安装并启动Grafana服务后,通常的配置文件路径是在以下两个位置之一: Homebrew默认配置文件路径:/usr/local/etc/grafana…...
在Spring Boot中集成RabbitMQ的完整指南
前言 在现代微服务架构中,消息队列(Message Queue)是实现异步通信、解耦系统组件的重要工具。RabbitMQ 是一个流行的消息中间件,支持多种消息协议,具有高可靠性和可扩展性。 本博客将详细介绍如何在 Spring Boot 项目…...
PostgreSQL 与 SQL 基础:为 Fast API 打下数据基础
在构建任何动态、数据驱动的Web API时,一个稳定高效的数据存储方案是不可或缺的。对于使用Python FastAPI的开发者来说,深入理解关系型数据库的工作原理、掌握SQL这门与数据库“对话”的语言,以及学会如何在Python中操作数据库,是…...
[QMT量化交易小白入门]-六十二、ETF轮动中简单的评分算法如何获取历史年化收益32.7%
本专栏主要是介绍QMT的基础用法,常见函数,写策略的方法,也会分享一些量化交易的思路,大概会写100篇左右。 QMT的相关资料较少,在使用过程中不断的摸索,遇到了一些问题,记录下来和大家一起沟通,共同进步。 文章目录 相关阅读1. 策略概述2. 趋势评分模块3 代码解析4 木头…...
中国政务数据安全建设细化及市场需求分析
(基于新《政务数据共享条例》及相关法规) 一、引言 近年来,中国政府高度重视数字政府建设和数据要素市场化配置改革。《政务数据共享条例》(以下简称“《共享条例》”)的发布,与《中华人民共和国数据安全法》(以下简称“《数据安全法》”)、《中华人民共和国个人信息…...
Flask和Django,你怎么选?
Flask 和 Django 是 Python 两大最流行的 Web 框架,但它们的设计哲学、目标和适用场景有显著区别。以下是详细的对比: 核心区别:哲学与定位 Django: 定位: "全栈式" Web 框架。奉行"开箱即用"的理念。 哲学: "包含…...

World-writable config file /etc/mysql/mysql.conf.d/my.cnf is ignored
https://stackoverflow.com/questions/53741107/mysql-in-docker-on-ubuntu-warning-world-writable-config-file-is-ignored 修改权限 -> 重启mysql # 检查字符集配置 SHOW VARIABLES WHERE Variable_name IN (character_set_server, character_set_database ); --------…...

【汇编逆向系列】四、函数调用包含单个参数之Double类型-mmword,movsd,mulsd,addsd指令,总结汇编的数据类型
一、汇编代码 上一节开始,讲到了很多debug编译独有的汇编方式,为了更好的区分release的编译器优化和debug的区别,从本章节开始将会提供debug和release的汇编用作对比 Debugb编译 single_double_param:00000000000000A0: F2 0F 11 44 24 08…...

Digital IC Design Flow
Flow介绍 1.设计规格 架构师根据市场需求制作算法模型(Algorithm emulation)及芯片架构(Chip architecture),确定芯片设计规格书(Chip design specification) 原型验证 原型验证(Prototype Validation)通常位于产品开发流程的前期阶段,主要是在设计和开发的初步阶…...
【CSS-6】深入理解CSS复合选择器:提升样式表的精确性与效率
CSS选择器是前端开发的基石,而复合选择器则是其中最强大且实用的工具之一。本文将全面解析CSS复合选择器的类型、用法、优先级规则以及最佳实践,帮助你编写更高效、更精确的样式表。 1. 什么是复合选择器? 复合选择器是通过组合多个简单选择…...
C++ vector容器存储对象和存储指针的区别(vector对象、vector指针)(存储指针时推荐使用智能指针)
文章目录 **1. 内存管理**- **存储对象**:- **存储指针**: **2. 生命周期控制**- **存储对象**:- **存储指针**: **3. 性能差异**- **存储对象**:- **存储指针**: **4. 使用场景**- **选择存储对象的情况**…...