Hash算法-对海量url判重

Posted 架构真经

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hash算法-对海量url判重相关的知识,希望对你有一定的参考价值。

前面给大家讲了哈希表(散列)这种数据结构,那么使用哈希表来解决实际问题,那就是Hash算法了,我们一起来看看。

一、Hash算法的概念

Hash算法(Hash Algorithm),简称散列算法,也成哈希算法(英译),是将一个大文件映射成一个小串字符。与指纹一样,就是以较短的信息来保证文件的唯一性的标志,这种标志与文件的每一个字节都相关,而且难以找到逆向规律。

举个列子:

服务器存了10个文本文件,你现在想判断一个新的文本文件和那10个文件有没有一个是一样的。你不可能去比对每个文本里面的每个字节,很有可能,两个文本文件都是5000个字节,但是只有最后一位有所不同,但这样的,你前面4999位的比较就是毫无意义。那一个解决办法,就是在存储那10个文本文件的时候,都将每个文件映射成一个hash字符串。服务器只需要存储10个hash字符串,在判断的时候,只需要判断新的这个文本文件的hash值是否和那10个文件的hash值一致,那就可以解决这个问题了。

由于文件是无限的,而映射后的字符串能表示的位数是有限的。因此可能会存在不同的key对应相同的Hash值。这就存在碰撞的可能。

二、Hash算法的应用

java的数据结构中,很多类都有用到hash算法,比如String,HashMap。

1.String中的hashCode方法

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

可以看到,这里使用了31来作为乘级因子,这是为什么呢?

  1. 选择数字31是因为它是一个奇质数,如果选择一个偶数,因为乘二相当于左移一位,可能会产生溢出,导致数值信息溢出。

  2. 这一点的优势并不明显,但这是一个传统(选择质数)。

  3. 同时,数字31有一个很好的特性,乘法运算可以被移位和减法运算来取代,来获取更好的性能,而且这一点可以由jvm来自动完成。31*i=(i<<5)-i

2.HashMap中hash值

存在的目的是加速键值对的查找,key的作用是为了将元素适当的放在各个桶里,对于抗碰撞的要求没那么高。

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

对key的hash计算,就是计算出key的hash值,并移动到低位,完成高低位的融合。

3.Hash在密码学中的应用

hash算法在密码学中主要是用于消息摘要和签名。换句话说,主要是对整个消息的完整性进行校验。

安全散列算法(英语:Secure Hash Algorithm,缩写为SHA),SHA家族的五个算法,分别是SHA-1、SHA-224、SHA-256、SHA-384,和SHA-512,由美国国家安全局(NSA)所设计,并由美国国家标准与技术研究院(NIST)发布;是美国的政府标准。后四者有时并称为SHA-2。

三、使用hash来解决字符串判重/字符串匹配问题

  1. 遇见不定长问题可通过二分+hash降低复杂度

  2. 遇见定长字符串问题可通过尺取+hash来降低复杂度

  3. 二维hash的时候尺取方法就是把之前不需要的都变为0再加上当前行,将匹配字符串整体下移,来验证hash值是否相等

PS:尺取法:顾名思义,像尺子一样取一段,尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。

例子1:

给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url?

腾讯面试题:A.txt和B.txt两个文件,A有1亿个qq号,B有100万个,用代码实现交、并、差…

1.思路

可以估计每个文件安的大小为5G×64=320G,远远大于内存限制的4G。所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法。

  1. 遍历文件a,对每个url求取hash(url)%1000,然后根据所取得的值将url分别存储到1000个小文件(记为a0,a1,…,a999)中。这样每个小文件的大约为300M。

  2. 遍历文件b,采取和a相同的方式将url分别存储到1000小文件(记为b0,b1,…,b999)。这样处理后,所有可能相同的url都在对应的小文件(a0vsb0,a1vsb1,…,a999vsb999)中,不对应的小文件不可能有相同的url。然后我们只要求出1000对小文件中相同的url即可。

  3. 求每对小文件中相同的url时,可以把其中一个小文件的url存储到hash_set中。然后遍历另一个小文件的每个url,看其是否在刚才构建的hash_set中,如果是,那么就是共同的url,存到文件里面就可以了

2.代码实现

参考自两个上亿行的大文件取交集

TODO:Java实现版本还未写

PS:如果允许小误差,也可以采用如下的布隆算法实现

import java.util.ArrayList;  
import java.util.BitSet;  
import java.util.List;  

/** 
 * BloomFilter算法 
 *  
 * @author JYC506 
 *  
 */
 
public class BloomFilter {  
    /*哈希函数*/ 
    private List<IHashFunction> hashFuctionList;  
    /*构造方法*/ 
    public BloomFilter() {  
        this.hashFuctionList = new ArrayList<IHashFunction>();  
    }  
    /*添加哈希函数类*/ 
    public void addHashFunction(IHashFunction hashFunction) {  
        this.hashFuctionList.add(hashFunction);  
    }  
    /*删除hash函数*/ 
    public void removeHashFunction(IHashFunction hashFunction) {  
        this.hashFuctionList.remove(hashFunction);  
    }  
    /*判断是否被包含*/ 
    public boolean contain(BitSet bitSet, String str) {  
        for (IHashFunction hash : hashFuctionList) {  
            int hashCode = hash.toHashCode(str);  
            if(hashCode<0){  
                hashCode=-hashCode;  
            }  
            if (bitSet.get(hashCode) == false) {  
                return false;  
            }  
        }  
        return true;  
    }  
    /*添加到bitSet*/ 
    public void toBitSet(BitSet bitSet, String str) {  
        for (IHashFunction hash : hashFuctionList) {  
            int hashCode = hash.toHashCode(str);  
            if(hashCode<0){  
                hashCode=-hashCode;  
            }  
            bitSet.set(hashCode, true);  
        }  
    }  

    public static void main(String[] args) {  
        BloomFilter bloomFilter=new BloomFilter();  
        /*添加3个哈希函数*/ 
        bloomFilter.addHashFunction(new JavaHash());  
        bloomFilter.addHashFunction(new RSHash());  
        bloomFilter.addHashFunction(new SDBMHash());  
        /*长度为2的24次方*/ 
        BitSet bitSet=new BitSet(1<<25);  
        /*判断test1很test2重复的字符串*/ 
        String[] test1=new String[]{"哈哈","我","大家","逗比","有钱人性","小米","Iphone","helloWorld"};  
        for (String str1 : test1) {  
            bloomFilter.toBitSet(bitSet, str1);  
        }  
        String[] test2=new String[]{"哈哈","我的","大家","逗比","有钱的人性","小米","Iphone6s","helloWorld"};  
        for (String str2 : test2) {  
            if(bloomFilter.contain(bitSet, str2)){  
                System.out.println("'"+str2+"'是重复的");  
            }  
        }  

    }  
}  
/*哈希函数接口*/ 
interface IHashFunction {  
    int toHashCode(String str);  
}  

class JavaHash implements IHashFunction {  

    @Override 
    public int toHashCode(String str) {  
        return str.hashCode();  
    }  

}  

class RSHash implements IHashFunction {  

    @Override 
    public int toHashCode(String str) {  
        int b = 378551;  
        int a = 63689;  
        int hash = 0;  
        for (int i = 0; i < str.length(); i++) {  
            hash = hash * a + str.charAt(i);  
            a = a * b;  
        }  
        return hash;  
    }  

}  

class SDBMHash implements IHashFunction {  

    @Override 
    public int toHashCode(String str) {  
        int hash = 0;  
        for (int i = 0; i < str.length(); i++)  
            hash = str.charAt(i) + (hash << 6) + (hash << 16) - hash;  
        return hash;  
    }  

}

例子2:

现有海量日志数据保存在一个超级大的文件中,该文件无法直接读入内存,要求从中提取某天出访问百度次数最多的那个IP。

  1. 从这一天的日志数据中把访问百度的IP取出来,逐个写入到一个大文件中;

  2. 注意到IP是32位的,最多有2^32个IP。同样可以采用映射的方法,比如模1000,把整个大文件映射为1000个小文件;

  3. 找出每个小文中出现频率最大的IP(可以采用hash_map进行频率统计,然后再找出频率最大的几个)及相应的频率;

  4. 在这1000个最大的IP中,找出那个频率最大的IP,即为所求。

参考分治法获取文件中出现频次最高100词

四、总结

  1. 简单点说,hash就是将任意长度的消息压缩成某一固定长度的消息摘要的函数。

  2. 可能会存在不同的key对应相同的Hash值,这就是存在碰撞的可能。

  3. Hash算法是不可逆的,即不同通过Hash值逆向推出key的值。

处理海量数据问题思路:

  1. 分而治之/hash映射 + hash统计 + 堆/快速/归并排序;

  2. 双层桶划分

  3. Bloom filter/Bitmap;

  4. Trie树/数据库/倒排索引;

  5. 外排序;

  6. 分布式处理之Hadoop/Mapreduce。


参考资料:

  1. https://blog.csdn.net/u014209205/article/details/80820263

  2. https://blog.csdn.net/qq_38891827/article/details/80723483

  3. https://www.cnblogs.com/wkfvawl/p/9016281.html

  4. https://blog.csdn.net/consciousman/article/details/52348439

  5. https://bbs.csdn.net/topics/370253735

  6. https://www.cnblogs.com/aspirant/p/7154551.html

  7. https://www.bbsmax.com/A/n2d9OwP4JD

  8. https://www.2cto.com/kf/201701/586765.html

  9. https://blog.csdn.net/weixin_33737774/article/details/85891625

  10. https://blog.csdn.net/qingdujun/article/details/82343756

  11. https://blog.csdn.net/u012173884/article/details/47419163

  12. https://blog.csdn.net/samjustin1/article/details/52251180


以上是关于Hash算法-对海量url判重的主要内容,如果未能解决你的问题,请参考以下文章

海量数据分析处理方法

基于hash的文档判重——simhash

如何从URL获取片段标识符(hash#之后的值)?

手写实现一致性Hash算法

在保持相同行为的同时隐藏 url 中的 #hash 片段?

第一弹HASH算法模板以及简单的入门题总结