常用分享--HASH算法解析

Posted 程序员地铁时光

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常用分享--HASH算法解析相关的知识,希望对你有一定的参考价值。


今日主题:hash算法的解析以及MessageDigest的使用

今日内容: 上一篇文章中主要是对一致性HASH算法进行解析,在选择HASH算法的使用,对于hash算法的选择有了很大疑惑,于是便对于所有的hash算法进行了简单的研究,并对提供信息摘要算法的MessageDigest类进行了研究


1.HASH函数介绍

  • 加法HASH

    所谓加法hash就是把输入的元素相加,标准的hash构造如下:

 /** * 加法HASH * * @param key hash的KEY * @param prime 任意质数 * @return */ private static int additiveHash(String key, int prime) { int hash, i; for (hash = key.length(), i = 0; i < key.length(); i++) { hash += key.charAt(i); } return (hash % prime); }

该算法的值域为:[0,prime-1]

  • 乘法HASH

    在JDK 5.0中String类型的hashCode()方法使用了乘法hash如下图,乘数是31,为什么是31呢?

    一般在设计哈希算法的时候,会选择一个特殊的质数,是可以降低哈希算法冲突的,31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。另外一些相近的质数,比如37、41、43等等,也都是不错的选择。而且31可以被 JVM 优化,31 * i = (i << 5) - i 啥意思呢?即乘法运算可以被移位和减法运算取代

常用分享--HASH算法解析

还有一些其他的著名的hash函数:

 // 32位FNV算法 private static final long OFFSET_BASIS = 2166136261L;// 32位offset basis private static final long PRIME = 16777619; // 32位prime
public static long hash(byte[] src) { long hash = OFFSET_BASIS; for (byte b : src) { hash ^= b; hash *= PRIME; } return hash; }     //RS_HASH public static long RS_Hash(String str) { int a = 63689; int b = 378551; long hash = 0; for (int i = 0; i < str.length(); i++) { hash = hash * a + str.charAt(i); //System.out.println(hash); a = a * b; //System.out.println(a); } return (hash & 0x7FFFFFFF);//32位 //return (hash & 0x7FFFFFFFFFFFFFFFL);//64位 }
  • 除法Hash

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

  • 位运算Hash

这类型Hash函数通过利用各种位运算(常见的是移位和异或)来充分的混合输入元素,

标准的旋转Hash的构造如下:

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

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

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

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

  • 混合HASH

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


2.MessageDigest来实现数据加密

MessageDigest 类为应用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。信息摘要是安全的单向哈希函数,它接收任意大小的数据,输出固定长度的哈希值

MessageDigest 对象开始被初始化。该对象通过使用update方法处理数据。任何时候都可以调用reset 方法重置摘要。一旦所有需要更新的数据都已经被更新了,应该调用 digest 方法之一完成哈希计算。

对于给定数量的更新数据,digest 方法只能被调用一次digest 被调用后,MessageDigest 对象被重新设置成其初始状态。

/** * @Author smile7up * @createDate 2019-0905 * @Description MessageDigest测试工具类 */public class MessageDigestTest {
public static void main(String[] args) throws NoSuchAlgorithmException { System.out.println(getMd5("1")); }
/** * 通过md5进行加密 * @param source 要加密的数据 * @return * @throws NoSuchAlgorithmException */ private static String getMd5(String source) throws NoSuchAlgorithmException { //1.获取MessageDigest对象 MessageDigest digest = MessageDigest.getInstance("md5");
//2.执行加密操作 byte[] bytes = source.getBytes();
//在MD5算法这,得到的目标字节数组的特点:长度固定为16 byte[] targetBytes = digest.digest(bytes);
//3.声明字符数组 char [] characters = new char[]{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
//4.遍历targetBytes StringBuilder builder = new StringBuilder(); for (byte b : targetBytes) { //5.取出b的高四位的值 //先把高四位通过右移操作拽到低四位 int high = (b >> 4) & 15;
//6.取出b的低四位的值 int low = b & 15;
//7.以high为下标从characters中取出对应的十六进制字符 char highChar = characters[high];
//8.以low为下标从characters中取出对应的十六进制字符 char lowChar = characters[low];
builder.append(highChar).append(lowChar); }
return builder.toString(); }}

运行结果为:

另附上即将使用的一致性hash算法的hash算法事例

/** * @Author smile7up * @createDate 2019-0905 * @Description hash算法 */public class HashFunction {
/** * 新建 */ private MessageDigest md5 = null;
public long hash(String key) { if (md5 == null) { try { md5 = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("no md5 algrithm found"); } }
md5.reset(); md5.update(key.getBytes()); byte[] bKey = md5.digest(); //具体的哈希函数实现细节--每个字节 & 0xFF 再移位 long result = ((long) (bKey[3] & 0xFF) << 24) | ((long) (bKey[2] & 0xFF) << 16 | ((long) (bKey[1] & 0xFF) << 8) | (long) (bKey[0] & 0xFF)); return result & 0xffffffffL;    } }


    本篇文章中主要是对于hash函数进行了简单的介绍,常用的六种hash函数构成了各种hash算法,下一篇文章,主要会对一致性hash算法的实现做分享


以上是关于常用分享--HASH算法解析的主要内容,如果未能解决你的问题,请参考以下文章

分享前端开发常用代码片段

片段(Java) | 机试题+算法思路+考点+代码解析 2023

收藏|分享前端开发常用代码片段

关于js----------------分享前端开发常用代码片段

[白话解析] 深入浅出一致性Hash原理

哈希表及其常用算法(代码实例)