What is 哈希?

news2024/11/29 12:54:50

哈希

前言:大一大二就一直听说哈希哈希,但一直都没有真正的概念:What is 哈希?这篇博客就浅浅聊一下作者认知中的哈希。

理解哈希

​ 哈希(Hash)也可以称作散列,实质就是一种映射,过程是将值作为参数输入到哈希函数中,通过一定的算法得到输出值,输入值和输出值几乎是一一对应的(注意这里的几乎),在这个过程中输入是无限的(任意长度),一样的输入对应一样的输出,不同的输入也有可能对应相同的输出(即哈希碰撞),最终结果具有离散均匀性。

根据以上过程可知哈希具有四个特点

①输入是无限的

②一样的输入对应一样的输出

③不同的输入也可能一样的输出(哈希碰撞)但概率很小

④离散均匀性

​ 这还是很抽象,接下来就举个栗子吧~~

​ 首先根据经典的哈希,假设两个条件:①无穷的输入域(例如任意长度字符串)②有限的输出域S(即输出的结果肯定在S范围中)

​ 现在假设我有一个函数Function(参数为整数),那么我可以试着输入任意数量任意大小整数都会有一个输出,例如Function(1234567)会有一个输出结果,当我再次调用Function(1234567)时输出结果不变(即函数内部不存在随机数的计算),如果给定参数不同,那么输出结果也有可能相同,原因是S是有限的输出域,而我有无限的输入,有可能导致不同的输入经过哈希函数计算得到相同的结果,一般碰撞概率很小,只有当输入数据极大量时才有可能产生碰撞,接着我用一个大圆来抽象表示输出域,输出结果抽象为一个个小黑点,大量数据输入后,产生的图像如下图所示。

在这里插入图片描述

​ 可以看到输出结果分布是非常离散且均匀的,此时若用一个小圆在输出域中采样,采样出的数量也是非常均匀的,like下图。

在这里插入图片描述

​ 那么能实现以上四个特点的这样一个Function函数就称为哈希函数,经典的哈希函数模板有 MD5 算法:返回值在0~264-1,SHAL算法:返回值在0~2128-1,java中的哈希函数返回值在0~232-1,哈希函数的具体实现算法先不用管(大牛搞出来的)。

加工

​ 日常使用哈希我们不希望输出域太大,此时我们就需要加工一下来使用,具体加工方法是在最后增加一个过程:即样本经过哈希函数得到的输出再模一个值m得到最终输出结果,如下图。

在这里插入图片描述

​ 此时最终的输出结果范围就在0~m-1上,并且结果在0~m-1上也同样均匀分布(很简单推),这样我们就人为缩小了哈希的结果域。

用途

问题:

假设有一个大文件,文件中有40亿个的无符号整数(范围在0~42亿+),若只给1G内存,内存中出现最多次的是哪个数?

题解

此时就有小朋友想用哈希表做了,HashMap中含有一个key和一个value,我们可以用key代表一个无符号整数,value代表这个无符号整数出现的次数,看起来这样好像能轻松解决这个问题。真的是这样吗?我们可以看到该问题给定的要求是1G内存,而在这题中,key的类型是int(4字节),value类型也考虑为int,那么一个HashMap结构至少占8个字节(不考虑哈希表内部索引空间浪费了,就假定为没有该空间),40亿个HashMap最坏情况(即每个数都不一样)那就是需要占用32G的内存,这远远超出了问题中所给定的要求。

那么这时,我们就可以加工了,直接把每一个结果取余100,就能将32G的文件分割为一百份,由于离散均匀性,我们可以将这一百份小文件看成大小相等的一百份,并且每个种类的key都只会出现在其中一份小文件中,那么每一份小文件就只占用32G / 100的内存,即满足题目要求,此时再分别统计所有小文件中的最大值求出最终结果。

哈希表的实现

​ 哈希表是怎么实现的呢,为什么能做到查询的时间复杂度是O(1),假设有一个大小为18的输出域,输入字符串“abc”、”bcd”、“xyz”,若经过哈希函数计算后取模18得到以下结果,可以发现,当得到相同值时,将他们接成一个链表就行。由于离散均匀性,每一个链的长度几乎是均匀变长的。哈希表查询的时候就是相同流程,计算出结果后去对应的位置遍历链表查询,此时就有一个问题,如果输出域太小,链表长度过长,那么查询的时候就无法做到O(1)的时间复杂度,所以当链表长度达到一个阈值,就触发扩容(扩容规则可以是变为两倍),18扩容为36,那么每个链表长度就变为一半,不过此时就需要把所有值重新计算(将模18改为用模36计算)放入输出域。

在这里插入图片描述

​ 说完实现原理,再说说增删查改时间复杂度O(1)到底是怎么来的?输入的值经过哈希函数计算这个过程时间复杂度是O(1),得到的值再进行模运算时间复杂度同样是O(1),假设链表长度是k,遍历链表的时间复杂度就是O(k),当我们让k的大小始终很小时,就能看作O(1),所以扩容就是为了保证时间复杂度维持在O(1)。

​ 接下来看看扩容的代价,假设有1000个字符串要加进去,设一开始哈希表格子大小为2,每次扩容容量乘2,那么需要经历log1000次扩容,所以扩容的次数理论来说是O(logkN),k为初始容量大小,N为要加入的内容大小,但实际情况远远小于logN,而每次重新计算哈希值的代价是O(N),因为每个值扩容后都要重新计算,故总的扩容代价是O(N*logN),那么单次代价即为O(logN)水平,当我将k的值稍微设计大一些,此时O(logN)就会变成一个特别小的代价值,实际情况N并不会过大,而且哈希表有增有删,删的情况还会减小扩容次数,故O(logN)可以逼近O(1)。Java等一些虚拟机语言还有离线扩容技术,可以不占用用户在线时间进行扩容,进一步加速了哈希表的使用,所以哈希表在实际使用是O(1),理论上是O(logN)。

​ 以上就是哈希表的原理,具体的实现改进还有很多,比如开放地址法等等,这里就不过多赘述。

Tips:java中的HashMap、HashSet就是用哈希原理实现的哈希表,HashMap就只是比HashSet多了一个伴随的value值,原理是相同的,算法时间复杂度为O(1);TreeMap和TreeSet即 java 中利用搜索树实现的 Map 和 Set,实际上用的是红黑树,而红黑树是一棵近似平衡的 二叉搜索树,算法时间复杂度为O(logN)。

布隆过滤器

布隆过滤器是来解决类似黑名单系统、爬虫去重问题的

比如一个网站的黑名单url有100亿个,每个url大小限制为64Byte。需要做到给定一个url,判断该url是否出现在黑名单中,且这个黑名单只有增加和查询的需求,没有删除需求

如果直接使用HashSet,那么占用空间大概是640G,十分浪费空间,或者搞成硬盘来记录url值,但这样慢的一p,所以咱们还是用内存来实现。

布隆过滤器允许一定的失误率,即有些不位于黑名单的url可能会被判定为在黑名单中(白 -> 黑),但不会出现原本位于黑名单中的url判定为不在黑名单中(黑 -> 白),而且通过人为的设计,可以让失误率很低,让工程上可以接受,例如爬网页,有些网页被杀掉了,没拿到信息,但在其他网页同样能得到信息。

那这么好用的布隆过滤器咋实现?

我们先来看看位图:

位图:一个数组,每个元素所占空间为1bit(bitarr bitmap)

假设我们想操作第178位,具体实现如下:

实现:使用基础类型拼凑

public class BitMap {
    public static void main(String[] args) {
        int a = 0;

        int[] arr = new int[10]; //32bit * 10 -> 320bits
        //arr[0] 0 ~ 31
        //arr[1] 32 ~ 63

        int i = 178; //想取第178位bit

        int numIndex = i / 32;
        int bitIndex = i % 32;

        //拿到第178位的状态
        int s = (arr[numIndex] >> bitIndex) & 1;

        //把第178位的状态改为1
        arr[numIndex] = arr[numIndex] | (1 << bitIndex);

        //把第178位的状态改为0
        arr[numIndex] = arr[numIndex] & (~(1 << bitIndex));       
    }
}

布隆过滤器就是一个大位图,长度为m的bitarr(实际占用空间为m / 8字节)

加入黑名单的步骤:对于每一个url来说,用k个不同的哈希函数算出k个哈希值out,然后将每一个out模m,得到对应于位图中的位置,将该位置设置为1,可以想象成将这些位置描黑,而取k个哈希函数相当于取k个特征,特征越多就越精确,就像一张图片你需要多取一些特征点才能归类为猫而不是归类为动物,从而使结果更精准。

查询黑名单的步骤:对于某一个url来说,算出k个哈希值,然后模m算出对应于位图中的位置,只有这些位置全是1(也就是都被描黑了),这个url才处于黑名单中,只要有一个位置不是1,就代表其不在黑名单中

决定失误率p的因素:位图大小m哈希函数个数k。n(样本量)假定为100亿,若m越大,p则会越小(类似于反比例函数);当m由p-m图像确定后,k较小时,随着k的增加,p会下降,但到达某一个临界(与m有关)时,随着k的增加,p会上升(类似于二次函数),因为k逼近m,空间占用率太大了。

在这里插入图片描述

————————————————p(失误率)与m(位图大小)的关系图————————————————

在这里插入图片描述

————————————————p(失误率)与k(哈希函数个数)的关系图————————————————

布隆过滤器只和样本量n失误率p这两个参数有关,与单样本大小无关(例如url大小),因为最后会被转化为哈希值,只要保证哈希函数能接受这个数值即可。

三个相关的公式:
m = − n ∗ l n p ( l n 2 ) 2 b i t ( 向上取整 ) k = l n 2 ∗ m n ≈ 0.7 ∗ m n 个 ( 向上取整 ) p 真 = ( 1 − e − n ∗ k 真 m 真 ) k 真 m=-\frac{n*lnp}{(ln2)^2}\quad bit(向上取整)\\ k=ln2*\frac{m}{n}≈0.7*\frac{m}{n}\quad 个(向上取整)\\ p_真=(1-e^{-\frac{n*k_真}{m_真}})^{k_真} m=(ln2)2nlnpbit(向上取整)k=ln2nm0.7nm(向上取整)p=(1emnk)k
当类似于黑名单这个集合结构系统,不需要删除行为,允许有一定失误率,那么就需要设计布隆过滤器,设计布隆过滤器只需要以上三个公式。

已经有的条件

①样本量n

②失误率p

对于上述黑名单示例,经计算后m的大小理论值大概是26G(原本需要640G),极大地减小了内存的占用,实际使用可以多申请一点比如申请32G内存,实际失误率会更小,计算就是第三个公式。

一致性哈希(Google改变世界技术三架马车之一)

一致性哈希原理:

一致性哈希是用来讨论分布式数据服务器(多台机器)怎么组织的问题

经典组织方式:假设有3台服务器,那么就先将数据转化为哈希值,再模3,分配到对应的服务器上,可以做到数据种类的均匀分配

负载均衡是由高频、中频、低频各自是否达到一定的数量来决定,此时由哈希函数是可以将这些数据均分的(根据离散均匀性,此时哈希函数需要选择种类较多的key,比如人名、身份证,而不适于选择类似于国家名的key)

经典结构的问题:若增加机器或减少机器,所有数据都需要重新计算哈希值,因此数据迁移的代价是全量的(比如原本模3,加了一个机器之后,就需要将全部数据模4重新计算分配,所以像MySQL这种一台机器的就搞不了,太慢了)

**一致性哈希的处理方式:**将哈希值的域比作一个环,先使用一个特定于服务器的哈希函数为每个服务器计算一个哈希值,将这些机器插入到这个环中。此时数据分配的方式是,这个数据所计算出的哈希值在环上顺时针方向最近的机器。实现方式是通过二分的方式查找大于等于当前哈希值中最近的机器,若没有比当前哈希值大的机器,则代表是最小的那个机器(因为是环)逻辑图如下。

在这里插入图片描述

在一致性哈希下,若新增或删减了服务器,则只需要迁移部分的数据即可完成数据的迁移,因为此时只用考虑增加或删减的那台机器与邻近机器的那一段数据。

例如下图,新增一个结点五,只需要把结点四到结点五之间的数据从节点一要回给结点五即可。

在这里插入图片描述

一致性哈希的问题:

① 当机器较少时,可能做不到这些机器将环均分
② 即使可以做到机器数量较少时将环均分,也无法保证增加或减少机器后负载均衡

解决方式:虚拟结点技术

虚拟结点技术本质上是按比例去抢环。假设有3台机器,则给这3台机器每个分配1000个字符串,此时不再让机器去占环上的位置,而让虚拟结点去占,即每台机器计算1000个哈希值,让这1000个哈希值占环上。此时这些虚拟结点之间的数据迁移可以用很简单的方式实现。当增加服务器时,则同理给这台服务器添加1000个虚拟结点,由于哈希分配均匀,因此它会均匀地从前3台机器中夺取数据到自己身上。减少机器也同理,减少机器时,自己的数据会均匀地分配到其他机器上。

一致性哈希还可以管理负载。例如若某台机器比较强,则可以分配更多的虚拟结点,而某台机器较弱,则可分配较少的虚拟结点。

以上就是关于作者对哈希的学习和理解,真的很想描述清楚分享给大家,能力有限,觉得写的不好不要见怪,有问题或错误欢迎评论区指出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1166123.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

git更改远程仓库地址

1、输入命令【git remote -v】查看当前git远程仓库地址 2、输入命令【git remote set-url origin 新地址】替换成新地址 3、输入命令【git remote -v 】查看是否更新成功

Odoo 网站主题开发指南

Odoo 网站主题开发指南 下载根据本指南开发的主题模块源码 Odoo 网站生成器是一个灵活的工具&#xff0c;可以轻松构建与 Odoo 应用完全集成的网站。使用其提供的主题选项 (options) 和构建块 (blocks) 很容易定制网站。然而&#xff0c;你还可以更进一步深度定制。在本文中&a…

Zinx框架-游戏服务器开发002:按照三层结构模式重构代码功能-待续

文章目录 1 Zinx框架总览2 三层模式的分析3 三层重构原有的功能 - 头文件3.1 通道层Stdin和Stdout类3.1.2 StdInChannel3.1.2 StdOutChannel 3.2 协议层CmdCheck和CmdMsg类3.2.1 CmdCheck单例模式3.2.1.1 单例模式3.2.1.2 * 命令识别类向业务层不同类别做分发 3.2.2 CmdMsg自定…

Python+pandas将Excel文件xlsx批量转换xls(代码全注释)

文章目录 专栏导读背景安装的库代码部分(全注释)视频演示总结&#x1f44d; 该系列文章专栏&#xff1a;[Python办公自动化专栏]PS: xls转xlsx文章在这&#xff1a;【点我直达】 专栏导读 &#x1f338; 欢迎来到Python办公自动化专栏—Python处理办公问题&#xff0c;解放您的…

MySQL缩短查询时间小技巧

背景 今天我要统计数据表的最新更新时间&#xff0c;有些表数据量特别大&#xff0c;所以统计比较费时间&#xff0c;但是如果使用一下小技巧&#xff0c;就会极大加快查询时间&#xff0c;适合小白的调优手段。 查询更新时间 select max(update_time) from test大概表的行数…

外汇天眼:账户惨遭冻结?一招帮你解决问题!

在外汇交易中&#xff0c;难免会出现大大小小的问题&#xff0c;其中账户被冻结也是经常能够见到的。首先摒弃外汇黑平台&#xff0c;在正规的外汇平台&#xff0c;如果出现账户被冻结的问题&#xff0c;可能是以下几种原因&#xff1a; 第一种是使用平台禁止的辅助软件进行薅…

python-opencv写入视频文件无法播放

python-opencv写入视频文件无法播放 在采用Python写OpenCV的视频时&#xff0c;生成的视频总是无法播放&#xff0c;大小只有不到两百k&#xff0c;播放器提示视频已经损坏。网上搜了一些方法&#xff0c;记录下解决办法。 代码如下 fourcc cv2.VideoWriter_fourcc(*MJPG) fp…

CRM客户关系管理系统源码 PHP客户管理系统源码

CRM客户关系管理系统源码 PHP客户管理系统源码 系统采用&#xff1a;PHPMYSQL 开发&#xff0c;功能完善&#xff0c;界面美观。 功能介绍&#xff1a; 1.客户管理&#xff1a;客户列表、今日新增客户、近7天新增客户、本月新增客户、新增客户&#xff08;包括客户名称、所在…

青柚课堂|为3960余名学生解惑青春期

2023年10月30日-11月1日&#xff0c;益阳市海棠学校邀请益阳市蚂蚁社会工作服务中心的儿童性教育公益讲师们开展了以“我们的青春期”为主题的“青柚课堂”性教育公益讲座&#xff0c;授课初中部班级72个&#xff0c;受益青少年3960余人。 在热身游戏的活跃氛围中&#xff0c;讲…

投标之---信用中国查询信用

https://www.creditchina.gov.cn/ 挨个点 &#xff0c;有的需要跳转网页&#xff0c;输入企业信息就是可以的&#xff0c;具体按照招标要求&#xff0c;如果没有具体的&#xff0c;那就是多查询几个

【Python3】【力扣题】225. 用队列实现栈

【力扣题】题目描述&#xff1a; 栈&#xff1a;线性集合。后进先出。 队列&#xff1a;线性集合。先进先出。也有双端队列和循环队列。 【Python3】代码&#xff1a; 1、解题思路&#xff1a;两个队列。队列1存储元素&#xff0c;队列2辅助。元素从队尾进入队列2&#xff0c;…

身份证二要素核验API:提高身份验证的精确性与效率

前言 在数字时代&#xff0c;身份验证已经成为各行各业的重要环节。无论是金融交易、电子商务还是在线服务&#xff0c;确保用户身份的准确性至关重要。身份证二要素核验API&#xff0c;作为一种先进的技术解决方案&#xff0c;正在逐渐崭露头角&#xff0c;为身份验证带来了精…

springboot中使用redis管理session

前言 使用软件&#xff1a; redis&#xff1a; linux版本下载 windows版本下载 安装redis 下载redis http://download.redis.io/releases/ 源码安装redis&#xff08;ubuntu&#xff09; #将指定版本的redis上传到服务器#解压 sudo tar -xzvf redis-6.2.4.tar.gzcd re…

家用小型洗衣机哪款性价比高?公认好用四款内衣洗衣机推荐

小型的内衣洗衣机由于体积小巧&#xff0c;而且实用&#xff0c;非常适合没有太多空闲时间的上班族以及小型住户的使用。想要挑选到一款能够满足每日清洗需要&#xff0c;同时拥有便携与高效率的小型内衣洗衣机&#xff0c;也许会让你选择得有些烦恼。我们为大家挑选了一些性价…

仿真建模工具:AnyLogic 8.8.5 Crack

多方法建模环境&#xff0c;使用所有三种现代模拟方法开发模型&#xff1a; 系统动力学 这三种方法可以任意组合使用&#xff0c;通过一种软件来模拟任何复杂性的业务系统。在 AnyLogic 中&#xff0c;您可以使用各种可视化建模语言&#xff1a;流程图、状态图、动作图以及库存…

数据结构与算法之美学习笔记:14 | 排序优化:如何实现一个通用的、高性能的排序函数?

目录 前言如何选择合适的排序算法&#xff1f;如何优化快速排序&#xff1f;举例分析排序函数 前言 本节课程思维导图&#xff1a; 几乎所有的编程语言都会提供排序函数&#xff0c;比如 C 语言中 qsort()&#xff0c;C STL 中的 sort()、stable_sort()&#xff0c;还有 Java …

游戏中的-雪花算法

1、什么是雪花算法&#xff1f; 雪花算法&#xff08;Snowflake&#xff09;是一种生成唯一ID的算法。在游戏开发过程中会为玩家生成唯一的id&#xff0c;或者玩家获得一件装备&#xff0c;为这件装备生成唯一的Id&#xff0c;将此角色和装备的Id保存于数据库当中。 全局唯一性…

Spring-AOP-面向切面编程

文章目录 目录 文章目录 前言 一 . 场景设定和问题复现 二 . 解决技术[代理模式] 2.1 代理模式 2.2 静态代理 2.3 动态代理 三 . 面向切面编程思想(AOP) 3.1 面向切面编程思想 3.2 AOP 思想的应用场景 3.3 AOP术语名词介绍 3.3.1 横切关注点 3.3.2 通知(增强) 3…

面试算法52:展平二叉搜索树

题目 给定一棵二叉搜索树&#xff0c;请调整节点的指针使每个节点都没有左子节点。调整之后的树看起来像一个链表&#xff0c;但仍然是二叉搜索树。 分析 看起来需要按照节点的值递增的顺序遍历二叉搜索树中的每个节点&#xff0c;并将节点用指向右子节点的指针连接起来。这…

观测云产品更新 | 单点登录新增 OIDC / Oauth2.0 协议、数据转发优化、场景优化等

观测云更新 数据转发 1. 新增【观测云】选项&#xff0c;支持将数据保存到观测云的对象存储&#xff0c;计费逻辑按天出账&#xff0c;每天统计存储的压缩后的数据大小&#xff0c;按照每 GB 0.007 元出具对应账单。 2. 数据转发菜单导航位置调整至【管理】模块&#xff0c;…