哈希函数是什么
一种将任意长度的输入映射为固定长度输出的函数。这个输出通常称为哈希值、散列值或摘要。
也就是说将一个数据,可以是任何类型,数字、字符串输入,返回一个固定长度的二进制数据。
比方说,输入"hello 世界",返回一串儿固定长度的二进制数据, 0001010010101 之类的
这里有几个新手误区
- 哈希函数返回的原始数据是二进制数据
- 二进制是给机器看的,在高级编程语言中直接打印可能会有乱码的
- 再次说明,哈希函数的结果值不是字符串!
哈希函数的结果
以"hello 世界"的哈希结果值010100100为例
010100100是一个纯粹的二进制数据,不夹杂任何业务,它不是10进制的164,也不是10进制164对应的ASCII值,也不是10进制164对应的UTF-8编码值,所以你直接打印是会出现乱码的
准确来说,它就是一个不代表任何下游业务的纯二进制值!!!
在下游业务中,哈希函数的结果值,最终是要存储的,例如存储到数据库中
以哈希函数的md5实现来说,比方说md5("hello 世界"),返回的是01000101010……二进制,直接将这个值存储到数据库中,是没有问题的。
但是将01000101010……这玩意儿存数据库,人看起来起来太累了,大家都通常将其转换为16进制b4bfe0a9f9281daa04ab2649e1059732,在业务层这就是个字符串,存储起来就方便多了
所以大家千万不要误解为哈希函数的结果是一个字符串,这是不正确的,这只是业务层的加工而已,你非要把它转化为10进制、8进制也是可以的
再次重申,哈希函数的结果值
- 二进制数据
- 没有任何业务意义
就好比你讲哈希值01000101010……转化成16进制它就成了编程语言中的字符串类型,你自己写一套编码映射表你把它转化成表情包也行,这是业务自身的事情!
md5函数算法只是哈希函数的其中一种实现,还有很多其他的哈希函数,例如 sha1、sha256、sha512 等,不同哈希函数返回结果值是不同的,长度也会不同
一个具体案例
以这个在线哈希的网站为例https://www.sojson.com/md5/,支持32位[大]32位[小]16位[大]16位[小],其中 “大/小” 的意思是出现字母时,字母的大小写
md5算法的结果输出是 128 位(16 字节)的二进制哈希值,对hello world进行md5,结果为
- 16个字节的二进制,我转化成10进制方便查看
[94, 182, 59, 187, 224, 30, 238, 208, 147, 203, 34, 184, 245, 172, 220, 51],刚好16个字节 - 对每一个字节转化成16进制,例如
94->5e``182->b6``59->3b…,最后拼接成1个字符串5eb63bbbe01eeed093cb22bb8f5acdc3,大写就是5EB63BBBE01EEED093CB22BB8F5ACDC3 - md5的结果是16个字节,16进制的位数是2个字符,
16x2,刚好是32位的md5业务值 - 16位的md5值是什么❓,去掉32位md5业务值的前8位和后8位,
5eb63bbbe01eeed093cb22bb8f5acdc3,中间这个就是16位的md5值
加密
加密是什么?能支持解密的才能称为加密
字符串md5是一种哈希算法的实现的哈希值十六进制显示是cc5548b16df30f116f72304c32738f5a
你在数据库里存储了这条记录,这时你知道这个哈希值是的原始值是md5是一种哈希算法的实现
但如果只给你cc5548b16df30f116f72304c32738f5a,你是无法倒推出其原始值的
这正是哈希函数的特性
- 通过哈希结果值无法倒推出原始值,也就无法作为加密函数使用
哈希函数的结果应用
以javac#c++python这四个值计算其哈希为例
| 编号 | 原值 | 哈希值 | 哈希冲突 |
|---|---|---|---|
| 1 | java | 10100100 | ❌ |
| 2 | c# | 10111101 | |
| 3 | c++ | 1111001 | |
| 4 | python | 10100100 | ❌ |
java和python的哈希值是相同的,这是正常情况(几率很低),这种就称为哈希冲突,在下游业务应用时要实现100%的匹配精准,所以会有一些业务层的二次处理,例如开放寻址、链地址法等,
但底层哈希算法中,一个值对应世界上一个唯一的哈希值,目前还无法技术实现
还有一点要注意的是,一个值哈希值是幂等的,一年前执行md5("hello world")哈希值是value1,一年后执行md5("hello world")哈希值依旧是value1
一种最出色的实现,散列表O(1)复杂度查询,思想嵌入了几乎所有的编程语言及数据库
- 底层O(1)原理: https://blog.csdn.net/qq_37485347/article/details/154653182
- 编程体现现: https://blog.csdn.net/qq_37485347/article/details/142171291
go 语言的实现
MurmurHash3 哈希函数
MurmurHash3 是一个出色的哈希函数实现,主要特点是快,冲突碰撞少
哈希函数执行快也就意味着其性能高,适合高频次大量运算
packagehashimport("github.com/spaolacci/murmur3")funcHash(data[]byte)uint64{returnmurmur3.Sum64(data)}这个包github.com/spaolacci/murmur3对哈希结果值(二进制)进行了转化,默认支持返回uint32uint64两种类型
output:7236082610745944782
md5 哈希函数
标准库 [crypto.md5] 哈希函数,返回值进行过了转化,将二进制数据转化成了[]byte类型
packagehashimport("crypto/md5")funcMd5(data[]byte)[]byte{digest:=md5.New()digest.Write(data)// 流式写入returndigest.Sum(nil)}使用时
funcTestMurmur3(t*testing.T){text:="hello, world!\n"md5:=Md5([]byte(text))t.Log(md5)// t.Log 默认10进制显示}直接打印会输出 output[145 12 139 199 49 16 176 205 27 197 210 188 174 120 37 17]
如果程序写成
funcTestMurmur3(t*testing.T){text:="hello, world!\n"md5:=Md5([]byte(text))t.Log(string(md5))}这种会输出乱码的,前面已经说过了,哈希函数的返回值是二进制数据,没有业务意义,它不是utf-8/unicode/ascii码的值,就是一个纯粹的二进制数据
标准库将其转化成了[]byte,转化后的[]byte自然也是没有任何业务意义的
❓string()用法是干嘛的❓
- 字节数组➡️字符的编码转化
- 按 UTF-8/ASCII/unicode 规则解析每个字节
- 字节数组本身是合法字符编码(如 UTF-8/ASCII 字符串的字节)时使用
比方说拿一个不是UTF-8字符的[]byte,非要使用string(),那肯定会出现乱码,不是乱码也不是想要的值
go 中的存储展示
开发者大多喜欢将其转为十进制数字类型或者十六进制字符串类型存储
- 16进制有abcd的字符,编程语言中体现为字符串类型
- 10进制就是编程语言中的int类型了
例如
funcMd5(data[]byte)[]byte{digest:=md5.New()digest.Write(data)// 流式写入returndigest.Sum(nil)}funcTestMurmur3(t*testing.T){text:="hello, world!\n"md5:=Md5([]byte(text))// 16进制输出t.Log(hex.EncodeToString(md5))// t.Logf("%x", md5)// 10进制输出t.Logf("%d",md5)}output:
910c8bc73110b0cd1bc5d2bcae782511 [145 12 139 199 49 16 176 205 27 197 210 188 174 120 37 17]- 转换16进制时,尽量使用标准库的
hex包,性能要比占位符%x方式方式要高 hex.EncodeToString会对[]byte转换16进制后的值拼接,所以才看到了一个字符串