哈希函数
哈希函数性质:
- 1.输入域无穷大;
- 2.输出域有穷大;
- 3.若输入相等,则输出相等;
- 4.若输入不相等,输出也可能相等(哈希碰撞)
- 5.输出域上均匀分布
哈希函数特征:
- 1.与输入规律无关;
- 2.哈希函数生成16位的码每一位都是相互独立的(因此利用一个哈希函数可造出很多哈希函数)
哈希表的复杂度
- 哈希表的增删改查时间复杂度是 O(1)
哈希表的扩容复杂度log以n为底N,n为扩容的倍数,比如每次扩大为原来的两倍,故时间复杂度为㏒₂N,很低,次数也少。
另外还可以用离线扩容的机制,当发现hash节点上的链表长度(经典哈希表结构)超过一定值时,在线使用的时候仍正常向原hash表中放值,但可以在不使用时,扩容,重新计算hash放入新哈希表中。
利用以上的各种优化技巧,因此哈希表的增删改查复杂度可以认为是O(1)
- 造出多个hash函数的方式:可以通过将某一个hash函数,乘上某个系数,再加上某个常数得到一个新的hahs函数
- 利用哈希函数做分流,处理大数据问题
极大文件中,找出重复的行
将大文件的每一行hash之后取1000的模,这样相同的字符串一定会落入同一台机器上,在同一台机器判读(可再hash,更细化一些)
设计RandomPool结构
初级6-00:56
【题目】 设计一种结构,在该结构中有如下三个功能:insert(key):将某个key加入到该结构,做到不重复加入。delete(key):将原本在结构中的某个key移除。 getRandom():等概率随机返回结构中的任何一个key。
【要求】 Insert、delete和getRandom方法的时间复杂度都是O(1)
package com.zuogod.java;
import java.util.HashMap;
/**
* @author quanquan
* @create 2020-04-30-13:24
*/
//使用两个哈希表
public class RandomPool {
public static class Pool<K>{
private HashMap<K, Integer> keyIndexMap;
private HashMap<Integer, K> indexKeyMap;
private int size;
public Pool(){
this.keyIndexMap = new HashMap<K, Integer>();
this.indexKeyMap = new HashMap<Integer, K>();
this.size = 0;
}
public void insert(K key){
if (!this.keyIndexMap.containsKey(key)){
this.keyIndexMap.put(key,this.size);
this.indexKeyMap.put(this.size++,key);
}
}
// 为了能够保证getRandom严格的等概率,remove中最重要的一个操作,就是把最后一个值填补至remove的元素的位置
// 否则,一remove,hash表上就一个洞,即无法等概率返回了。
public void remove(K key){
if(this.keyIndexMap.containsKey(key)){
int deleteIndex = keyIndexMap.get(key);
int lastIndex = --size;
K lastKey = this.indexKeyMap.get(lastIndex);
this.keyIndexMap.put(lastKey,deleteIndex);
this.indexKeyMap.put(deleteIndex,lastKey);
this.keyIndexMap.remove(key);
this.indexKeyMap.remove(lastIndex);
}
}
public K getRandom(){
if (this.size == 0){
return null;
}
int index = (int)(Math.random() * this.size); //0 ~ size-1
return this.indexKeyMap.get(index);
}
}
public static void main(String[] args) {
Pool<String> pool = new Pool<String>();
pool.insert("zuo");
pool.insert("cheng");
pool.insert("yun");
pool.remove("zuo");
System.out.println(pool.getRandom());
System.out.println(pool.getRandom());
System.out.println(pool.getRandom());
System.out.println(pool.getRandom());
System.out.println(pool.getRandom());
}
}
\
布隆过滤器
- 本质为一个比特(Bit,是Binary digit(二进制数)位)数组
- 会有一定的失误率,不会把本该命中的的判不命中,但可能把不中的判中
如上图,想要将第30000位置的比特描黑,的代码:
使用的是int,4个字节,32位
public static void main(String[] args){
int[] arr = new int[1000]; //长度1000的整数数组,可以表示的比特位有32000个
//也可以使用long,那就可以表示64000位
//也可使用矩阵来描黑这个“坐标系”中的点 long[][] map = new long[1000][1000] 即 1000*1000*64个
int index = 30000; //计划将index位置描黑
int intIndex = index / 32; //算出在第几个整数
int bitIndex = index % 32; //模 算出在整数的第几个比特位
//将1左移bitIndex位,即第bitIndex位为1(其余位为0)或运算 arr[intIndex],即将第intIndex个整数的bitIndex位(总共的第30000位)描黑
arr[intIndex] = (arr[intIndex]|(1 << bitIndex));
}
比如说有个url的黑名单,有100亿个,每次需要查看某个url是否在这个黑名单中。
- 是可以使用前一小节讲到的利用hash函数分流的方式,将如此大数量分割至不同机器,分布式处理
- 那么使用布隆过滤器的方式:
假如准备的比特位有m位,准备K个hash函数,将100亿的每个url,通过第一个hash1函数计算,然后% m取模(取余),将该位置描黑;
再使用hash2计算url,然后%模m,将该位置也描黑;
..... 一直使用到K个hash函数,将k个位置都描黑,。 则一个url算是在该过滤器中记录完成。
在判断的时候,一个陌生url进来,经过k个hash函数判断,其每个对应位是否都是黑的,是则名中,否则不命中
- 从上面的设计方式来看,其失误率p,位数m大小,K大小之间是有关系的。 数学公式:
n为样本量,p为预期失误率,K为hash函数的个数:
需要的Bit位数 m:m = - (n * lognp) / (logn2)2
需要的byte字节数为:m/8
哈希函数的个数:K = ln2 * m/n = 0.7 * m/n
数组大小和哈希函数个数向上取整,真实失误率为:(1 - e ^ (- n*K/m))^K
- 上例实际计算如下图:
勘误,上面的16G应该是22.3G,忘记平方了