Hash函数是指把一个大范围映射到一个小范围。把大范围映射到一个小范围的目的往往是为了节省空间,使得数据容易保存。

除此以外,Hash函数往往应用于查找上。所以,在考虑使用Hash函数之前,需要明白它的几个限制:

1. Hash的主要原理就是把大范围映射到小范围;所以,你输入的实际值的个数必须和小范围相当或者比它更小。不然冲突就会很多。
2. 由于Hash逼近单向函数;所以,你可以用它来对数据进行加密。
3. 不同的应用对Hash函数有着不同的要求;比如,用于加密的Hash函数主要考虑它和单项函数的差距,而用于查找的Hash函数主要考虑它映射到小范围的冲突率。

Hash函数应用的主要对象是数组(比如,字符串),而其目标一般是一个int类型。以下我们都按照这种方式来说明。

一般的说,Hash函数可以划分为如下几类:
1. 加法Hash

2. 位运算Hash

3. 乘法Hash

4. 除法Hash

5. 查表Hash

6. 混合Hash

7.数组Hash

下面详细的介绍以上各种方式在实际中的运用。

一、几种经典的Hash函数实现

1、加法Hash

所谓的加法Hash就是把输入元素一个一个的加起来构成最后的结果。标准的加法Hash的构造如下:

1  static int additiveHash(String key, int prime)
2 {
3 int hash, i;
4 for (hash = key.length(), i = 0; i < key.length(); i++)
5 hash += key.charAt(i);
6 return (hash % prime);
7 }

这里的prime是任意的质数,看得出,结果的值域为[0,prime-1]。

2、位运算Hash

这类型Hash函数通过利用各种位运算(常见的是移位和异或)来充分的混合输入元素。比如,标准的旋转Hash的构造如下:

1  static int rotatingHash(String key, int prime)
2 {
3 int hash, i;
4 for (hash=key.length(), i=0; i<key.length(); ++i)
5 hash = (hash<<4)^(hash>>28)^key.charAt(i);
6 return (hash % prime);
7 }

先移位,然后再进行各种位运算是这种类型Hash函数的主要特点。比如,以上的那段计算hash的代码还可以有如下几种变形:

1.     hash = (hash<<5)^(hash>>27)^key.charAt(i);
2. hash += key.charAt(i);
hash += (hash << 10);
hash ^= (hash >> 6);
3. if((i&1) == 0)
{
hash ^= (hash<<7) ^ key.charAt(i) ^ (hash>>3);
}
else
{
hash ^= ~((hash<<11) ^ key.charAt(i) ^ (hash >>5));
}
4. hash += (hash<<5) + key.charAt(i);
5. hash = key.charAt(i) + (hash<<6) + (hash>>16) – hash;
6. hash ^= ((hash<<5) + key.charAt(i) + (hash>>2));

3、乘法Hash

这种类型的Hash函数利用了乘法的不相关性(乘法的这种性质,最有名的莫过于平方取头尾的随机数生成算法,虽然这种算法效果并不好)。比如,

1  static int bernstein(String key)
2 {
3 int hash = 0;
4 int i;
5 for (i=0; i<key.length(); ++i) hash = 33*hash + key.charAt(i);
6 return hash;
7 }

jdk5.0里面的String类的hashCode()方法也使用乘法Hash。不过,它使用的乘数是31。推荐的乘数还有:131, 1313, 13131, 131313等等。

使用这种方式的著名Hash函数还有:

 1  //  32位FNV算法
2 int M_SHIFT = 0;
3 public int FNVHash(byte[] data)
4 {
5 int hash = (int)2166136261L;
6 for(byte b : data)
7 hash = (hash * 16777619) ^ b;
8 if (M_SHIFT == 0)
9 return hash;
10 return (hash ^ (hash >> M_SHIFT)) & M_MASK;
11 }

以及改进的FNV算法:

 1 public static int FNVHash1(String data)
2 {
3 final int p = 16777619;
4 int hash = (int)2166136261L;
5 for(int i=0;i<data.length();i++)
6 hash = (hash ^ data.charAt(i)) * p;
7 hash += hash << 13;
8 hash ^= hash >> 7;
9 hash += hash << 3;
10 hash ^= hash >> 17;
11 hash += hash << 5;
12 return hash;
13 }

除了乘以一个固定的数,常见的还有乘以一个不断改变的数,比如:

 1 static int RSHash(String str)
2 {
3 int b = 378551;
4 int a = 63689;
5 int hash = 0;
6
7 for(int i = 0; i < str.length(); i++)
8 {
9 hash = hash * a + str.charAt(i);
10 a = a * b;
11 }
12 return (hash & 0x7FFFFFFF);
13 }

虽然Adler32算法的应用没有CRC32广泛,不过,它可能是乘法Hash里面最有名的一个了。关于它的介绍,大家可以去看RFC 1950规范。

4、除法Hash

除法和乘法一样,同样具有表面上看起来的不相关性。不过,因为除法太慢,这种方式几乎找不到真正的应用。需要注意的是,我们在前面看到的hash的 结果除以一个prime的目的只是为了保证结果的范围。如果你不需要它限制一个范围的话,可以使用如下的代码替代”hash%prime”: hash = hash ^ (hash>>10) ^ (hash>>20)。

5、查表Hash

查表Hash最有名的例子莫过于CRC系列算法。虽然CRC系列算法本身并不是查表,但是,查表是它的一种最快的实现方式。查表Hash中有名的例子有:Universal Hashing和Zobrist Hashing。他们的表格都是随机生成的。

6、混合Hash

混合Hash算法利用了以上各种方式。各种常见的Hash算法,比如MD5、Tiger都属于这个范围。它们一般很少在面向查找的Hash函数里面使用。

7、数组hash

1 inline int hashcode(const int *v)
2 {
3 int s = 0;
4 for(int i=0; i<k; i++)
5 s=((s<<2)+(v[i]>>4))^(v[i]<<10);
6 s = s % M;
7 s = s < 0 ? s + M : s;
8 return s;
9 }

二、对Hash算法的评价

http://www.burtleburtle.net/bob/hash/doobs.html 这个页面提供了对几种流行Hash算法的评价。我们对Hash函数的建议如下:

1. 字符串的Hash。最简单可以使用基本的乘法Hash,当乘数为33时,对于英文单词有很好的散列效果(小于6个的小写形式可以保证没有冲突)。复杂一点可以使用FNV算法(及其改进形式),它对于比较长的字符串,在速度和效果上都不错。

2. 长数组的Hash。可以使用http://burtleburtle.net/bob/c/lookup3.c这种算法,它一次运算多个字节,速度还算不错。

三、后记

本文简略的介绍了一番实际应用中的用于查找的Hash算法。Hash算法除了应用于这个方面以外,另外一个著名的应用是巨型字符串匹配(这时的 Hash算法叫做:rolling hash,因为它必须可以滚动的计算)。设计一个真正好的Hash算法并不是一件容易的事情。做为应用来说,选择一个适合的算法是最重要的。

注:虽说以上的hash能极大程度地避免冲突,但是冲突是在所难免的。所以无论用哪种hash函数,都要加上处理冲突的方法。

Hash函数浅谈的更多相关文章

  1. Javascript-回调函数浅谈

    回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数.回调函数不是由该函数的实现方直接调用,而是在特定 ...

  2. C# eval()函数浅谈

    <%# Bind("Subject") %> //绑定字段 <%# Container.DataItemIndex + 1%> //实现自动编号<%# ...

  3. 继承虚函数浅谈 c++ 类,继承类,有虚函数的类,虚拟继承的类的内存布局,使用vs2010打印布局结果。

    本文笔者在青岛逛街的时候突然想到的...最近就有想写几篇关于继承虚函数的笔记,所以回家到之后就奋笔疾书的写出来发布了 应用sizeof函数求类巨细这个问题在很多面试,口试题中很轻易考,而涉及到类的时候 ...

  4. 从SG函数浅谈解决博弈问题的通法

    基于笔者之前对于几种二元零和博弈游戏的介绍,这里将其思想进行简单的提炼,并引出解决这类二元零和博弈游戏的强大工具——SG函数. 其实对于博弈游戏如Bash.Nim等基本类型,异或一些比较高级的棋类游戏 ...

  5. Sql Server存储过程和函数浅谈

    今天给大家总结一下sql server中的存储过程和函数.本人是小白,里面内容比较初级,大神不喜勿喷 自行飘过就是.. 首先给大家简单列出sql server中的流控制语句,后面会用到的^_^ sql ...

  6. 从Java继承类的重名static函数浅谈解析调用与分派

    在java中,static成员函数是否可以被重写呢? 结论是,你可以在子类中重写一个static函数,但是这个函数并不能像正常的非static函数那样运行. 也就是说,虽然你可以定义一个重写函数,但是 ...

  7. 浅谈Hash函数

    什么是hash函数: hash函数也可以翻译成“散列”函数,一般就使用音译“哈希”函数,简单的说哈希函数是对任意长度的输入进行的压缩映射,所谓的压缩映射顾名思义,输出通常来说要比输入短,并且得到的输出 ...

  8. $.ajax()方法详解 ajax之async属性 【原创】详细案例解剖——浅谈Redis缓存的常用5种方式(String,Hash,List,set,SetSorted )

    $.ajax()方法详解   jquery中的ajax方法参数总是记不住,这里记录一下. 1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为Str ...

  9. 浅谈字符串Hash

    浅谈字符串Hash 本篇随笔讲解Hash(散列表)的一个重要应用:字符串Hash. 关于Hash Hash是一种数据结构,叫做Hash表(哈希表),也叫散列表.关于Hash的实现,其实与离散化颇为类似 ...

随机推荐

  1. CSS 基础面试题

    1 介绍一下标准的CSS的盒子模型?与低版本IE的盒子模型有什么不同的? 标准盒子模型:宽度=内容的宽度(content)+ border + padding + margin 低版本IE盒子模型:宽 ...

  2. 实验6:Mapreduce实例——WordCount

          实验目的1.准确理解Mapreduce的设计原理2.熟练掌握WordCount程序代码编写3.学会自己编写WordCount程序进行词频统计实验原理MapReduce采用的是“分而治之”的 ...

  3. ios问题笔记

    32位 最多内存0到3G 64位 最多内存0到8G iOS模板code4app.com github.com developer.apple.con 动画 label不能变小 只能变大,(而uivie ...

  4. 理解Java方法增强

    在实际开发中,我们往往需要对某些方法进行增强,常用的方法增强的方式有三种. 类继承 .方法覆盖 必须控制对象创建,才能使用该方式 装饰者模式方法加强 必须和目标对象实现相同接口或继续相同父类,特殊构造 ...

  5. day 35

    目录 单表操作 分组 group by having order by limit 使用顺序 多表操作 外键 一对多 多对多 一对一 多表联查 单表操作 分组 group by 分组指的是:将所有记录 ...

  6. AWS之EC2搭建WordPress博客

    AWS之搭建WordPress博客 注意:请确定您已经成功完成LAMP架构的搭建; 1.下载并解压WordPress安装包: ①使用wget命令在WordPress官网获取最新安装包: [ec2-us ...

  7. Spring管理连接池实验出现错误(c3p0)

    配置文件: 测试文件: 出现异常报错:

  8. C++编译错误 --- 成员函数定义在 .h 文件中出现重定义错误(Error LNK 2005)

    今天写了一个简单的类,定义在 .h 文件中, 类很简单就将其成员函数定义在了一起(class类后面).运行的时候出现了如下图所示的编译错误(error LNK2005) 查资料,大部分都是说需要加上 ...

  9. linux 环境下 apache tomcat 安装jenkins

    参考文档: https://blog.51cto.com/12629984/1980034 https://www.cnblogs.com/lxs1314/p/8567652.html https:/ ...

  10. 201871010123-吴丽丽 《面向对象程序设计(Java)》第八周学习总结

    201871010123-吴丽丽<面向对象程序设计(Java)>第八周学习总结 项目 内容   这个作业属于哪个课程 http://www.cnblogs.com/nwnu-daizh/  ...