梁越

一文吃透哈希

0 人看过

字符哈希算法,IP哈希算法,bloom过滤器原理

哈希结构

哈希算法的两个关键:哈希函数、冲突解决

哈希函数

哈希函数的设计有很多很多,例如常见的

  1. 取余,关键字除以某个不大于散列表表长m的数p,p<=m,p一般取小于m的第一个素数
  2. 平方取中,取关键字平方后中间几位数
  3. 直接寻址,使用某个线性函数,例如a*k+b
  4. MD4, MD5, SHA等算法,包括后面提到的某些字符哈希的算法

冲突解决

经过哈希函数后,可能会有冲突,解决冲突的常见方法有

  1. 链表法,每个对应的桶拉一个链表
  2. 再哈希法,使用第二个哈希函数一直哈希到不冲突为止,或者就直接线性探测,位置不停加1,直到不冲突,或者平方探测

拉链法结构的哈希表

采用拉链法结构的哈希表如下:

字符哈希算法

有很多场景需要对字符串做哈希的,一般常见的做法:

  1. 加法hash

    int additiveHash(string key, int prime) //prime是个质数
    {
    int hash, i;
    for (hash = key.size(), i = 0; i < key.size(); i++)
    hash += key[i];
    return (hash % prime);
    }
  2. 位运算hash,这是IP哈希用到的算法,但是和一般的有些区别,下面详细说

    int rotatingHash(string key, int prime)
    {
    int hash, i;
    for (hash = key.size(), i = 0; i < key.size(); i++)
      hash = (hash<<4)^(hash>>28)^key[i];
    return (hash % prime);
    }
  3. 乘法hash

    int bernstein(string key)
    {
    int hash = 0;
    int i;
    for (i=0; i<key.size(); ++i) hash = 33*hash + key[i];
    return hash;
    }
  4. 以及其他很多改进算法FVN,MD5和SHA算法,MD5可以产生出一个128位(16字节)的散列值,SHA256对于任意长度的消息都会产生一个256bit长的哈希值

IP哈希算法

参考https://leetcode-cn.com/circle/discuss/l8fl8B/

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

unsigned ipToInt(string ip) {
    int l = ip.size();
    vector<int> ipList;
    //split
    for (int i = 0; i < l; i++) {
        int j = i;
        while (j < l && ip[j] != '.') j++;
        ipList.push_back(stoi(ip.substr(i, j - i)));
        i = j;
    }
    int n = ipList.size();
    unsigned res = 0;
    for (int i = 0; i < n; i++) {
        res = res << 8 | ipList[i];
    }
    return res;
}

string intToIp(unsigned num) {
    vector<string> ipList;
    string res = "";
    for(int i = 0; i < 4; i ++) {
        string seg = to_string(num & 255);
        ipList.push_back(seg);
        num = num >> 8;
    }
    reverse(ipList.begin(), ipList.end());
    for(int i = 0; i < 4; i ++) {
        if(i == 3) res += ipList[i];
        else res += ipList[i] + '.';
    }
    return res;
}
int main()
{
    string ip;
    unsigned num;
    cin >> ip;
    cin >> num;
    cout << ipToInt(ip) << endl;
    cout << intToIp(num) << endl;
}

bloom过滤器

布隆过滤器其实底层也是哈希,底层是一个bitset,每个字符串会通过哈希函数生成k个数字,对应bitset上的k个位,如果这些位都为1,说明字符可能出现,注意,只是可能,不是一定,所以关键就在于要怎么减少冲突的可能性。

在这里有一个共识,假如k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率即错误率,他们有如下关系

应用场景

在实际工作中,布隆过滤器常见的应用场景如下:

  1. 网页爬虫对 URL 去重,避免爬取相同的 URL 地址;
  2. 反垃圾邮件,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱;
  3. Google Chrome 使用布隆过滤器识别恶意 URL;
  4. Medium 使用布隆过滤器避免推荐给用户已经读过的文章;
  5. Google BigTable,Apache HBbase 和 Apache Cassandra 使用布隆过滤器减少对不存在的行和列的查找。
  6. 除了上述的应用场景之外,布隆过滤器还有一个应用场景就是解决缓存穿透的问题。所谓的缓存穿透就是服务调用方每次都是查询不在缓存中的数据,这样每次服务调用都会到数据库中进行查询,如果这类请求比较多的话,就会导致数据库压力增大,这样缓存就失去了意义。

利用布隆过滤器我们可以预先把数据查询的主键,比如用户 ID 或文章 ID 缓存到过滤器中。当根据 ID 进行数据查询的时候,我们先判断该 ID 是否存在,若存在的话,则进行下一步处理。若不存在的话,直接返回,这样就不会触发后续的数据库查询。需要注意的是缓存穿透不能完全解决,我们只能将其控制在一个可以容忍的范围内。

mysql对URL做索引(长字符做索引)

  1. hash索引
    使用crc32哈希函数转化为int

  2. 前缀索引
    直接取字符串的前几位(注意区别度)作为索引字段