哈希算法篇 - 布隆过滤器

简介: 哈希算法篇 - 布隆过滤器

之前写了个布隆过滤器,用于千万级新闻 url 去重。如果不了解可以看这里: 布隆过滤器:公众号地址随时看

如果建立写了布隆过滤器,大家在面试的时候,肯定都会被问到Hash的知识,以下是面试场景:

  • 你用的哈希函数是什么?
  • 你还知道哪些哈希函数?

多种哈希函数介绍

常用的字符串Hash函数还有ELFHash,APHash等等,都是十分简单有效的方法。这些函数使用位运算使得每一个字符都对最后的函数值产生影响。另外还有以MD5和SHA1为代表的杂凑函数,这些函数几乎不可能找到碰撞。

常用字符串哈希函数有 BKDRHash,APHash,DJBHash,JSHash,RSHash,SDBMHash,PJWHash,ELFHash 等等。对于以上几种哈希函数,我对其进行了一个小小的评测。

其中数据1为100000个字母和数字组成的随机串哈希冲突个数。数据2为100000个有意义的英文句子哈希冲突个数。数据3为数据1的哈希值与1000003(大素数)求模后存储到线性表中冲突的个数。数据4为数据1的哈希值与10000019(更大素数)求模后存储到线性表中冲突的个数。


经过比较,得出以上平均得分。平均数为平方平均数。可以发现,BKDRHash无论是在实际效果还是编码实现中,效果都是最突出的。APHash也是较为优秀的算法。DJBHash,JSHash,RSHash与SDBMHash各有千秋。PJWHash与ELFHash效果最差,但得分相似,其算法本质是相似的。


在信息修竞赛中,要本着易于编码调试的原则,个人认为BKDRHash是最适合记忆和使用的。

代码

C代码

附:各种哈希函数的C语言程序代码

unsigned int SDBMHash(char *str)
{
  unsigned int hash = 0;

  while (*str)
  {
    // equivalent to: hash = 65599*hash + (*str++);
    hash = (*str++) + (hash << 6) + (hash << 16) - hash;
  }

  return (hash & 0x7FFFFFFF);
}

// RS Hash Function
unsigned int RSHash(char *str)
{
  unsigned int b = 378551;
  unsigned int a = 63689;
  unsigned int hash = 0;

  while (*str)
  {
    hash = hash * a + (*str++);
    a *= b;
  }

  return (hash & 0x7FFFFFFF);
}

// JS Hash Function
unsigned int JSHash(char *str)
{
  unsigned int hash = 1315423911;

  while (*str)
  {
    hash ^= ((hash << 5) + (*str++) + (hash >> 2));
  }
   
  return (hash & 0x7FFFFFFF);
}

// P. J. Weinberger Hash Function
unsigned int PJWHash(char *str)
{
  unsigned int BitsInUnignedInt = (unsigned int)(sizeof(unsigned int) * 8);
  unsigned int ThreeQuarters  = (unsigned int)((BitsInUnignedInt  * 3) / 4);
  unsigned int OneEighth    = (unsigned int)(BitsInUnignedInt / 8);
  unsigned int HighBits    = (unsigned int)(0xFFFFFFFF) << (BitsInUnignedInt - OneEighth);
  unsigned int hash      = 0;
  unsigned int test      = 0;

  while (*str)
  {
    hash = (hash << OneEighth) + (*str++);
    if ((test = hash & HighBits) != 0)
    {
      hash = ((hash ^ (test >> ThreeQuarters)) & (~HighBits));
    }
  }

  return (hash & 0x7FFFFFFF);
}

// ELF Hash Function
unsigned int ELFHash(char *str)
{
  unsigned int hash = 0;
  unsigned int x  = 0;

  while (*str)
  {
    hash = (hash << 4) + (*str++);
    if ((x = hash & 0xF0000000L) != 0)
    {
      hash ^= (x >> 24);
      hash &= ~x;
    }
  }

  return (hash & 0x7FFFFFFF);
}

// BKDR Hash Function
unsigned int BKDRHash(char *str)
{
  unsigned int seed = 131; // 31 131 1313 13131 131313 etc..
  unsigned int hash = 0;

  while (*str)
  {
    hash = hash * seed + (*str++);
  }

  return (hash & 0x7FFFFFFF);
}

// DJB Hash Function
unsigned int DJBHash(char *str)
{
  unsigned int hash = 5381;

  while (*str)
  {
    hash += (hash << 5) + (*str++);
  }

  return (hash & 0x7FFFFFFF);
}

// AP Hash Function
unsigned int APHash(char *str)
{
  unsigned int hash = 0;
  int i;

  for (i=0; *str; i++)
  {
    if ((i & 1) == 0)
    {
      hash ^= ((hash << 7) ^ (*str++) ^ (hash >> 3));
    }
    else
    {
      hash ^= (~((hash << 11) ^ (*str++) ^ (hash >> 5)));
    }
  }

  return (hash & 0x7FFFFFFF);
}

Java代码:BKDR Hash

本篇主要介绍 BKDR Hash

package temp;

/**
 * JavaPub
 */

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class BKDRHash {
    public static int seed = 31; // 31 131 1313 13131 131313 etc..

    public static int getHashCode(String str) {
        int hash = 0;
        for (int i = 0; i != str.length(); ++i) {
            char c = str.charAt(i);
            hash = seed * hash + c;
        }
        return hash;
    }

    public static void main(String[] args) {
        int length = 100000;
        String str1 = "https://gitee.com/rodert/JavaPub";
        System.out.println("str1 :" + str1.hashCode() + " system");
        System.out.println("str1 :" + getHashCode(str1) + " custom");

        Map<String, String> map = new HashMap<String, String>();
        for (int i = 0; i != length; ++i) {
            String str = UUID.randomUUID().toString();
            map.put(getHashCode(str) + "", str);
        }
        System.out.println("冲突数为: " + (length - map.size()));
    }

}

ELF Hash详细分析

// ELF Hash Function
unsigned int ELFHash(char *str)
{
    unsigned int hash = 0;
    unsigned int x = 0;
    while (*str)
    {
        hash = (hash << 4) + (*str++);//hash左移4位,当前字符ASCII存入hash
        if ((x = hash & 0xF0000000L) != 0)
        {//如果最高的四位不为0,则说明字符多余7个,如果不处理,再加第九个字符时,第一个字符会被移出,因此要有如下处理。
            //该处理,如果对于字符串(a-z 或者A-Z)就会仅仅影响5-8位,否则会影响5-31位,因为C语言使用的算数移位
            hash ^= (x >> 24);
            //清空28-31位。上面其实就是把即将删除的高四位和低5-8位运算一次,和 hash = (hash << 4) + (*str++); 效果相同
            hash &= ~x;
        }
    }
    //返回一个符号位为0的数,即丢弃最高位,以免函数外产生影响。(我们可以考虑,如果只有字符,符号位不可能为负)
    return (hash & 0×7FFFFFFF);

详解 ELF Hash

文字一点都不多,认真阅读理解透彻,大有裨益

ELFhash 函数在 UNIX 系统 V 版本4 中的“可执行链接格式 ”( Executable and Linking Format,即ELF ) 中会用到,ELF 文件格式用于存储可执行文件与目标文件。ELFhash 函数是对字符串的散列。它对于长字符串和短字符串都很有效,字符串中每个字符都有同样的作用,它巧妙地对字符的 ASCII 编码值进行计算,ELFhash函数对于能够比较均匀地把字符串分布在散列表中。
说明:unsigned int hash = 0; unsigned int x = 0;
定义无符号整数,在进行位运算时无需考虑符号位的影响,左移和右移均补位0
int 为32位 ,即  00000000  00000000   00000000   00000000
hash = (hash << 4) + (*str++);//hash 左移4位,当前字符 ASCII 存入 hash
例,如果 hash 为2时,(hash << 4)操作后,放大16(2的4次方)倍;然后加上 (*str++),(*str++) 为8位的字符,所以对 4-7 为有影响,其后四位添到hash左移空出的四位。
if ((x = hash & 0xF0000000L) != 0)
0xF0000000L 表示28-31位这4位是1,后28为均为0的长整型(L),该操作的结果为 x 保存 hash 的高4位
& 按位与 如果两个相应的二进制位都为 1,则该位的结果值为1,否则为0
hash ^= (x >> 24);
首先x的拷贝进行右移23位的操作,然后与hash进行异或操作。
右移后X的值为 00000000 00000000 00000000  ****0000  ;**** 为 hash 的高四位
^ 按位异或 若参加运算的两个二进制位值相同则为 0,否则为 1
hash &= ~x;
有 if ((x = hash & 0xF0000000L) != 0),x 保存着hash 的高四位,虽然进行右移操作,但不会改变x的值,而是对副本进行操作。经过 hash &= ~x;  hash 的高四位被清空。
//返回一个符号位为0的数,即丢弃最高位,以免函数外产生影响。(我们可以考虑,如果只有字符,符号位不可能为负)
return (hash & 0×7FFFFFFF);


总结

简单来说:函数使用位运算使得每一个字符都对最后的函数值产生影响。

布隆过滤器的哈希函数原理就写到这里,希望大家看完之后以后面试碰到这题会没有困难!

另外,写原创技术文章不易,要花费好多时间和精力,希望大家看到文章也能有所收获!你们的点赞和收藏就能成为我继续坚持输出原创文章的动力!

目录
相关文章
|
28天前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
41 3
|
1月前
|
存储 算法 C#
C#哈希查找算法
C#哈希查找算法
|
1月前
|
算法 安全 Go
Python与Go语言中的哈希算法实现及对比分析
Python与Go语言中的哈希算法实现及对比分析
40 0
|
1月前
|
存储 算法 C++
【算法】哈希映射(C/C++)
【算法】哈希映射(C/C++)
|
3月前
|
算法 安全 JavaScript
安全哈希算法:SHA算法
安全哈希算法:SHA算法
53 1
安全哈希算法:SHA算法
|
3月前
|
JavaScript 算法 前端开发
国标哈希算法基础:SHA1、SHA256、SHA512、MD5 和 HMAC,Python和JS实现、加盐、算法魔改
国标哈希算法基础:SHA1、SHA256、SHA512、MD5 和 HMAC,Python和JS实现、加盐、算法魔改
480 1
|
4月前
|
缓存 负载均衡 算法
(四)网络编程之请求分发篇:负载均衡静态调度算法、平滑轮询加权、一致性哈希、最小活跃数算法实践!
先如今所有的技术栈中,只要一谈关于高可用、高并发处理相关的实现,必然会牵扯到集群这个话题,也就是部署多台服务器共同对外提供服务,从而做到提升系统吞吐量,优化系统的整体性能以及稳定性等目的。
|
5月前
|
存储 算法 安全
深入理解SHA系列哈希算法:安全性的保障与演进
深入理解SHA系列哈希算法:安全性的保障与演进
|
5月前
|
存储 算法 Java
Java查找算法概览:二分查找适用于有序数组,通过比较中间元素缩小搜索范围;哈希查找利用哈希函数快速定位,示例中使用HashMap存储键值对,支持多值关联。
【6月更文挑战第21天】Java查找算法概览:二分查找适用于有序数组,通过比较中间元素缩小搜索范围;哈希查找利用哈希函数快速定位,示例中使用HashMap存储键值对,支持多值关联。简单哈希表实现未涵盖冲突解决和删除操作。
58 1
|
5月前
|
存储 算法 安全
MD5哈希算法:原理、应用与安全性深入解析
MD5哈希算法:原理、应用与安全性深入解析