1 : 哈希函数没有随机值
2 : 理论上的哈希碰撞是不可避免的(因为,输入域无限,输出域有限)
3 : 哈希函数具有离散性和均匀性
离散型指 : 即使是有规律的数字算出来的结果也是大不相同的
均匀性指 : 哈希函数算出来的结果总是均匀分布在输出 域中
1:对主要操作是查找
2:对数据没有逻辑上的要求
3:先让数据均匀分配
掌握常用的俩种就行
哈希表,就是一种运用哈希函数的数据结构
哈希表空间的使用-只和输入不同的数有关
哈希表在使用上是可以认为O(1)的但不是理论上-理论上就是logN的
给定一个输出域,通过哈希函数算出一个值,模一个值给定到输出域中,然后后来来的值以单链表的形式加到这个值的后面.
如果这个单链表的值的数量过大了,那就的扩容.也就是将原来的哈希表中的每一个值,重新算一遍哈希模一个新值,然后在以链表形式串起来.
哈希表查找和删除为什么是O(1),因为通过哈希函数算肯定是O(1)的(即使是这个时间长),而通过单链表查询的话(如果这个单链表很短,那么也认为是O(1),单链表长度一旦过长就出发扩容操作)
扩容的时间复杂度怎么算 ,假设一次扩容一倍,时间复杂度就是logN,那么总的给定N个数的时间复杂度O(NlogN),而单次扩容的时间复杂度O(NlogN) / N = logN 这是一个小常数,而且这个哈希表不一定一次只扩容1倍,那么通过增加扩容的数量,这个logK会更小,可以认为单次的logK = O(1)
而且Java有一种技术就是离线扩容技术,可以进一步加速这个时间复杂度(C++没有)
因为Java有jvm会保存哈希表,所以Java可以一边扩容一边使用老的哈希表,等新表扩容(不占用用户时间)完,在释放旧表使用新表
1:如果给定你1G的内存,给你40亿的数,要求里找出出现次数最多的数
解题思路:
首先经典解题思路,就是将所有数都加入到哈希表中然后去找,但是内存肯定会爆掉
怎样设计,就是将40亿的数都算一遍哈希值,然后模个100,放到0-99的文件(这些文件肯定是能放到内存中的)中,然后在这些文件中找出现次数最多的数,然后比较所有文件中出现次数最多的数.
原理:就是哈希相同的值一定会算到同一个文件中,不同的数会根据离散型均匀的分配到文件中.
2 : 哈希表题:设计一个哈希表,能够随机的返回哈希表中的每一个值
//实现哈希随机
public static class Pool<K>{
private HashMap<K,Integer> keyIndexMap;
private HashMap<Integer,K> indexKeyMap;
private int size;
public Pool(){
this.keyIndexMap = new HashMap<>();
this.indexKeyMap = new HashMap<>();
this.size = 0;
}
public void insert(K key){
if (!keyIndexMap.containsKey(key)){
this.keyIndexMap.put(key,size);
this.indexKeyMap.put(size++,key);
}
}
public void delete(K key){
if (keyIndexMap.containsKey(key)){
//记录位置,然后堵洞
int deleteIndex = keyIndexMap.get(key);
//将最后一个位置拿出来
int lastIndex = --size;
K lastKey = indexKeyMap.get(lastIndex);
//堵洞交换
keyIndexMap.put(lastKey,deleteIndex);
indexKeyMap.put(deleteIndex,lastKey);
keyIndexMap.remove(key);
indexKeyMap.remove(lastIndex);
}
}
public K getRondom(){
if (size == 0)return null;
int randomIndex =(int) (Math.random()*size);//0 ~ size -1
return indexKeyMap.get(randomIndex);
}
}
布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间
解决,类似于黑名单查找的问题(爬虫行为)-没有删除行为,允许一定的失误率(失误率很低)
失误率:假如一个号是黑号,那么这个黑号一定能被查找到,但是也允许一定的白号但是被误杀了
布隆过滤器是哈希表和位图的结合
先将字符串用字符串哈希算法映射到哈希表中
但是由于哈希冲突,我们可以把一个字符串用多个不同的字符串哈希算法同时映射在整个哈希表中
要判断一个字符串是否在这堆字符串中,我们可以算出这个字符串的位置,当且仅当这个字符串每个映射位置都是1的时候才表示存在,只要有一个位置为0,就表示不存在
俩个问题:用多少个哈希函数,位图到底定多大(m)
哈希函数:根据样本量和位图大小来订(类似于猜指纹,不能太多要不位图全部被填满了,太少可能不准确)
package bloomfilterdemo;
import java.util.BitSet;
/**
* @Author 12629
* @Description:
*/
class SimpleHash {
public int cap;//当前容量
public int seed;//随机
public SimpleHash(int cap,int seed) {
this.cap = cap;
this.seed = seed;
}
//根据seed不同 创建不能的哈希函数
int hash(String key) {
int h;
//(n - 1) & hash
return (key == null) ? 0 : (seed * (cap-1)) & ((h = key.hashCode()) ^ (h >>> 16));
}
}
public class MyBloomFilter {
public static final int DEFAULT_SIZE = 1 << 20;
//位图
public BitSet bitSet;
//记录存了多少个数据
public int usedSize;
public static final int[] seeds = {5,7,11,13,27,33};
public SimpleHash[] simpleHashes;
public MyBloomFilter() {
bitSet = new BitSet(DEFAULT_SIZE);
simpleHashes = new SimpleHash[seeds.length];
for (int i = 0; i < simpleHashes.length; i++) {
simpleHashes[i] = new SimpleHash(DEFAULT_SIZE,seeds[i]);
}
}
/**
* 添加元素 到布隆过滤器
* @param val
*/
public void add(String val) {
//让X个哈希函数 分别处理当前的数据
for (SimpleHash simpleHash : simpleHashes) {
int index = simpleHash.hash(val);
//把他们 都存储在位图当中即可
bitSet.set(index);
}
}
/**
* 是否包含val ,这里会存在一定的误判的
* @param val
* @return
*/
public boolean contains(String val) {
//val 一定 也是通过这个几个哈希函数去 看对应的位置
for (SimpleHash simpleHash : simpleHashes) {
int index = simpleHash.hash(val);
//只要有1个为 0 那么一定不存在
boolean flg = bitSet.get(index);
if(!flg) {
return false;
}
}
return true;
}
public static void main(String[] args) {
MyBloomFilter myBloomFilter = new MyBloomFilter();
myBloomFilter.add("hello");
myBloomFilter.add("hello2");
myBloomFilter.add("bit");
myBloomFilter.add("haha");
System.out.println(myBloomFilter.contains("hello"));
System.out.println(myBloomFilter.contains("hello3"));
System.out.println(myBloomFilter.contains("he"));
}
}
模拟实现2
import java.util.BitSet;
// 构建哈希函数
class SimpleHash {
//容量
private int cap;
//随机种子
private int seed;
public SimpleHash( int cap, int seed) {
this.cap= cap;
this.seed =seed;
}
/**
* 把当前的字符串转变为1个哈希值
* @param value
* @return
*/
public int hash(String value) {
int result=0 ;
int len= value.length();
for (int i= 0 ; i< len; i ++ ) {
result = seed* result + value.charAt(i);
}
return (cap - 1 ) & result;
}
}
public class BloomFilter {
private static final int DEFAULT_SIZE = 1 << 24 ;//方便哈希函数的计算
private static final int [] seeds = new int []{5,7, 11 , 13 , 31 , 37 , 61};
private BitSet bits; // 位图用来存储元素
private SimpleHash[] func; // 哈希函数所对应类
private int size = 0;
//初始化bits和func
public BloomFilter() {
bits= new BitSet(DEFAULT_SIZE);
func = new SimpleHash[seeds.length];
//把所有哈希对象进行初始化
for( int i= 0 ; i< seeds.length; i ++ ) {
func[i]=new SimpleHash(DEFAULT_SIZE, seeds[i]);
}
}
public void set(String value) {
if(null == value)
return;
for(SimpleHash f : func) {
bits.set(f.hash(value));
}
size++;
}
public boolean contains(String value) {
if(value ==null ) {
return false;
}
for(SimpleHash f : func) {
if(!bits.get(f.hash(value))){
return false;
}
}
return true;//会有误判
}
public static void main(String[] args) {
String s1 = "欧阳锋";
String s2 = "欧阳克";
String s3 = "金轮法王";
String s4 = "霍都";
BloomFilter filter=new BloomFilter();
filter.set(s1);
filter.set(s2);
filter.set(s3);
filter.set(s4);
System.out.println(filter.contains("杨过"));
System.out.println(filter.contains("金轮法王"));
}
}
这里不写了,看下这篇博客
https://zhuanlan.zhihu.com/p/129049724
所谓位图,就是用每一位来存放某种状态,适用于海量数据,整数,数据无重复的场景。通常是用来判断某个数据存不存在的
用来快速判断一个整数是否在一堆整数中
二进制用0和1来表示数据,位图根据0和1来存储对应的数据,可以大大节省存储空间,并具备排序特性。
public static void main(String[] args) {
int a =0;// a 32 bit
int [] arr =new int[10];// 32*10 -> 320bits数组
/* arr [0] int 0 - 31
* arr [1] int 32 - 63 */
int i =178;//想取第178个bit 状态
int numIndex = 178/32;//找到178所在的数字
int bitIndex = 178%32;//找到178所在数字中的位数
//拿到178位的状态
int s = ( (arr[numIndex] >> (bitIndex)) & 1);
//将178位的状态改成 1
arr[numIndex] = arr[numIndex] | ( 1 << (bitIndex) );
//将178位的状态改成 0
arr[numIndex] = arr[numIndex] & ( ~ 1 << (bitIndex) );
}
public class MyBitSet {
private byte[] elem;
public int usedSize;
public MyBitSet() {
//默认只给一个大小
elem = new byte[1];
}
/**
* n个比特位
* @param n
*/
public MyBitSet(int n) {
elem = new byte[n/8+1];
}
/**
*
* @param val 可以等价于 将数据的对应位置置为1
*/
public void set(int val) {
if(val < 0 ) {
throw new IndexOutOfBoundsException();
}
int arrayIndex = val/8;
int bitIndex = val % 8;
this.elem[arrayIndex] |= (1 << bitIndex);
usedSize++;
}
/**
* 测试该数字是否存在
* @param val
* @return
*/
public boolean get(int val) {
if(val < 0 ) {
throw new IndexOutOfBoundsException();
}
int arrayIndex = val/8;
int bitIndex = val % 8;
//判断 elem[index] 的对应位是不是1 是的话与的结果是不等于0的
if((this.elem[arrayIndex] & (1L << bitIndex)) != 0) {
return true;
}
return false;
}
/**
*
* @param val 可以等价于 将数据的对应位置置为0
*/
public void reSet(int val) {
if(val < 0 ) {
throw new IndexOutOfBoundsException();
}
int arrayIndex = val/8;
int bitIndex = val % 8;
this.elem[arrayIndex] &= ~(1L << bitIndex);
usedSize--;
}
/**
* 当前比特位有多少个1
* @return
*/
public int getUsedSize() {
return this.usedSize;
}
}
哈希切割
给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址? 与上题条件相同,
如何找到top K的IP?
位图应用