持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
哈希表
认识哈希函数
哈希碰撞
现实世界中,基本上传进来的东西都可以转换成字符串,如果传进来的是一个对象,那就拿它的内存地址做字符串
哈希函数没有任何随机的成分,它不是随机函数,就是不管任何时候,相同的输入一定导致相同的输出;但是输入域可以无穷大,输出域相对有限,所以势必会有两个不同的输入域对应同一个输出域。这就是哈希碰撞。
哈希函数最重要的性质
离散性(均匀性)
哈希函数跟输入的数据状况是解耦的,即使是大量长得很像的不同输入,经过哈希函数的处理后,也能保证每一个输出不同,并且均匀分布
即使一点点的不同,也能被哈希函数放大得特别大,原理我们没必要弄懂,会用相关的算法就可以了
哈希函数的作用
哈希表的设计
哈希表增删改查为啥是O(1)呢?
经典的哈希表实现都是数组加链表,也有优化的实现,数组加红黑树
数组是哈希表一个初始的桶区域,数组长度就是桶的个数,假设17,存记录的时候,先根据哈希函数算出一个哈希值(hashcode),然后再模17,得到的结果肯定是在 0 ~ 16 之间,然后挂在相应的区域下面;
如果多个记录模完后值一样,那就顺着链表往下挂记录
删除,修改和查询操作也一样,根据哈希函数,相同的输入会导致相同的输出,将得到的哈希值模完17后,找到数组对应的位置,然后顺着链表往下操作
如果一共N条记录的话,每一个桶就是N/17的长度,那么增删改查不是O(N)吗?因为链表的长度决定遍历时候的代价。
面对这个问题,于是就出现了哈希表的扩容操作
哈希表的扩容操作
如果链表的长度达到了一定长度(多大长度会认为操作比较慢呢?不知道,各个语言有自己的规定,我们这里假设是10),就在原数组的基础上扩充长度的一倍,之前是17,那么现在桶的个数就是34,然后之前的每一条记录都全部重新算哈希值,重新模上数组的长度,毫无疑问,扩容之后,链表的长度一定差不多是之前的一半
哈希表的增删改查单次代价为什么是O(1)
假设哈希表最挫时候的样子,啥叫最挫的样子,就是链表长度到2的时候就很难受,就需要扩容,那么就是每个桶只能存一条记录
所以,接下来,第2条记录需要扩容、第3条记录需要扩容、第5条记录需要扩容、第9条记录需要扩容、第17条记录需要扩容、第33条记录需要扩容、第65条记录需要扩容......总的来说是有logN次的扩容
那么总的扩容代价就是 O(2) + O(3) + O(5) + O(9) +...+ O(N),是一个等比数列,所以总的代价就是O(N),均摊后就是单次代价,就是O(1)。
既然最挫样子时候的代价都很不错了,那么初始数组长度不是1的时候就不用说了,因为扩容的次数也会变少
其余的优化也只是常数时间的优化,比如链表改用红黑树
布隆过滤器
一致性哈希
一致性哈希解决的就是增加或者减少机器让它的数据迁移不是全量的,而且还能做到负载均衡
代码
package com.harrison.class23;
import javax.xml.bind.DatatypeConverter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
/**
* @author Harrison
* @create 2022-05-26-12:59
* @motto 众里寻他千百度,蓦然回首,那人却在灯火阑珊处。
*/
public class Hash {
private MessageDigest hash;
public Hash(String algorithm) {
try {
hash = MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
public String hashCode(String input) {
return DatatypeConverter.printHexBinary(hash.digest(input.getBytes())).toUpperCase();
}
public static void main(String[] args) {
System.out.println("支持的算法 : ");
for (String str : Security.getAlgorithms("MessageDigest")) {
System.out.println(str);
}
System.out.println("=======");
String algorithm = "MD5";
Hash hash = new Hash(algorithm);
String input1 = "zuochengyunzuochengyun1";
String input2 = "zuochengyunzuochengyun2";
String input3 = "zuochengyunzuochengyun3";
String input4 = "zuochengyunzuochengyun4";
String input5 = "zuochengyunzuochengyun5";
// 输出的是16进制的整数
System.out.println(hash.hashCode(input1));
System.out.println(hash.hashCode(input2));
System.out.println(hash.hashCode(input3));
System.out.println(hash.hashCode(input4));
System.out.println(hash.hashCode(input5));
}
}
本文出自与哈希函数有关的结构