simHash海量去重java实现

simHash的概念及介绍,我就不赘述了,搜一下到处是,我也是查了一些资料加上自己业务需求,最后整理了一份java实现的工具方法,如有不妥指出,欢迎指出。

由于需要要求15天内的数据去重,日记越累数据比较多,所以采用了64位分四段的方式进行比较,但是为了较少缓存,在存储的时候转成了BigInteger类型进行存储,所以在计算海明距离的时候,用了两种方法实现。

话不多说,直接上代码吧:

package com.jandmin.test.simhash;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.hankcs.hanlp.seg.common.Term;
import com.hankcs.hanlp.tokenizer.IndexTokenizer;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.safety.Whitelist;
import org.springframework.stereotype.Component;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * <p>SimHash 去重实现类</p>
 *
 * @Author: JandMin
 * @since: 1.0.0
 * @Date: 2019/2/20
 */
@Component
public class SimHashHelper {

    /**
     * 指纹的长度
     */
    private int bitLength = 64;
    private int overCount = 5;

    /**
     * 十进制的指纹
     */
    private BigInteger intSimHash;

    /**
     * 二进制的指纹
     */
    private String strSimHash;

    /**
     * 二进制指纹的4个子指纹
     */
    private List<String> shortSimHashList;

    public BigInteger getIntSimHash(){
        return this.intSimHash;
    }
    public String getStrSimHash() {
        return this.strSimHash;
    }
    public List<String> getShortSimHashList() {
        return shortSimHashList;
    }
    public int getBitLength() {
        return bitLength;
    }

    /**
     * 下面这两个目前没有用到,保留下来是为了后期可扩展
     *
     * 停用的词性
     */
    private static Set<String>          stopNatures    = Sets.newHashSet();
    /**
     * 特殊词的权重
     */
    private static Map<String, Integer> weightOfNature = Maps.newHashMap();
    static {
        // 停用词性为w:标点
        stopNatures.add("w");
        // 这里将n:名词设置为2。(默认权重为1)
        weightOfNature.put("n",2);
    }

    public SimHashHelper(){}
    public SimHashHelper(int bitLength){
        this.bitLength = bitLength;
    }

    public static void main(String[] args) {
        String str1 = "新华社北京12月6日电  12月6日,结束了对西班牙、阿根廷、巴拿马、葡萄牙国事访问并出席二十国集团领导人第十三次峰会后,国家主席习近平回到北京。习近平主席夫人彭丽媛,中共中央政治局委员、中央书记处书记、中央办公厅主任丁薛祥,中共中央政治局委员、中央外事工作委员会办公室主任杨洁篪,国务委员兼外交部部长王毅,全国政协副主席、国家发展和改革委员会主任何立峰等陪同人员同机抵达。当地时间5日下午,习近平离开里斯本启程回国。葡萄牙外长席尔瓦等到机场送行。专机离开葡萄牙领空前,两架葡萄牙空军战机升空护航";
        String str2 = "新华社北京12月6日电  12月6日,在结束对西班牙、阿根廷、葡萄牙等国事访问并出席二十国集团领导人第十三次峰会后,国家主席习近平回到北京。习近平主席夫人彭丽媛,中共中央政治局委员、中央书记处书记、中央办公厅主任丁薛祥,中共中央政治局委员、中央外事工作委员会办公室主任杨洁篪,国务委员兼外交部部长王毅,全国政协副主席、国家发展和改革委员会主任何立峰等陪同人员同机抵达。当地时间5日下午,习近平离开里斯本启程回国。葡萄牙外长席尔瓦等到机场送行。专机离开葡萄牙领空前,两架葡萄牙空军战机升空护航。";
        String str3 = "杭州报社北京12月6日电  12月6日,在结束对西班牙、阿根廷、巴拿马、葡萄牙国事访问并出席二十国集团领导人第十三次峰会后,国家主席习近平回到北京。习近平主席夫人彭丽媛,中共中央政治局委员、中央书记处书记、中央办公厅主任丁薛祥,中共中央政治局委员、中央外事工作委员会办公室主任杨洁篪,国务委员兼外交部部长王毅,全国政协副主席、国家发展和改革委员会主任何立峰等陪同人员同机抵达。当地时间5日下午,习近平离开里斯本启程回国。葡萄牙外长席尔瓦等到机场送行。专机离开葡萄牙领空前,两架葡萄牙空军战机升空护航。";

        String str5 = "CTO 职场解惑指南系列(一)";//4059115178153530904    4
        String str6 = "CTO 职场解惑指南系列(二)";//4059115179294365212

        String str7 = "你妈妈喊你回家吃饭哦,回家罗回家罗";//4237218111462   1
        String str8 = "你妈妈叫你回家吃饭啦,回家罗回家罗";//4237218095078
        SimHashHelper simHashHelper1 = new SimHashHelper();
        System.out.println(simHashHelper1.simHash(str7));
        System.out.println(simHashHelper1.getIntSimHash());
        System.out.println(simHashHelper1.getIntSimHash(simHashHelper1.getStrSimHash()));
        System.out.println(simHashHelper1.getStrSimHash());

        SimHashHelper simHashHelper2 = new SimHashHelper();
        System.out.println(simHashHelper2.simHash(str8));
        System.out.println(simHashHelper2.getStrSimHash());
        System.out.println(simHashHelper2.getIntSimHash());
        System.out.println(simHashHelper2.getIntSimHash(simHashHelper2.getStrSimHash()));
        
        System.out.println("================================");
        System.out.println(simHashHelper2.hammingDistance(simHashHelper2.getStrSimHash(),simHashHelper1.getStrSimHash()));
        System.out.println(simHashHelper1.hammingDistance(simHashHelper2.getIntSimHash(),simHashHelper1.getIntSimHash()));

    }

    /**
     * 过滤
     * @date: 2019/2/20
     * @param content 待分词的文本
     * @return java.lang.String
     * @throws 
     */
    public String preProcess(String content) {
        if(StringUtils.isBlank(content)){
            return "";
        }
        // 过滤掉所有的HTML的tag
        content = Jsoup.clean(content, Whitelist.none());
        String[] strings = {"\n","\r","\\r","\\n","\\t","&nbsp;"," "};
        for (String s:strings) {
            content = content.replace(s,"");
        }
        content = content.replaceAll("[\\p{P}+~$`^=|<>~`$^+=|<>¥×]", "");
        return content;
    }

    /**
     * 主方法,计算simHash值
     * @date: 2019/2/20
     * @param content
     * @return java.util.List<java.lang.String>
     * @throws 
     */
    public List<String> simHash(String content) {
        content = preProcess(content);
        if(StringUtils.isBlank(content)){
            return Lists.newArrayList();
        }
        // 词的权重
        Map<String,Integer> wordCount = Maps.newHashMap();

        // 定义特征向量/数组
        int[] hashValue = new int[this.bitLength];

        // 分词:索引分词
        List<Term> indexList = IndexTokenizer.segment(content);
        for (Term term : indexList){
            String word = term.word;
            String nature = term.nature.toString();
            // 过滤停用词性
            if (stopNatures.contains(nature)) {
                continue;
            }
            // 加权
            if (wordCount.containsKey(word)) {
                int count = wordCount.get(word);
                if(count > this.overCount) {
                    continue;
                }
                wordCount.put(word,count+1);
            } else {
                wordCount.put(word,1);
            }

            // 计算hash,将每一个分词hash为一组固定长度的数列.比如 64bit 的一个整数.
            BigInteger t = this.hash(word);
            for (int i = 0; i < this.bitLength; i++) {
                BigInteger bitmask = new BigInteger("1").shiftLeft(i);
                // 对每一个分词hash后的数列进行判断,如果是1000...1,那么数组的第一位和末尾一位加1,
                // 中间的62位减一,也就是说,逢1加1,逢0减1.一直到把所有的分词hash数列全部判断完毕.
                int weight = 1;
                if (weightOfNature.containsKey(nature)) {
                    weight = weightOfNature.get(nature);
                }
                if (t.and(bitmask).signum() != 0) {
                    // 这里是计算整个文档的所有特征的向量和
                    hashValue[i] += weight;
                } else {
                    hashValue[i] -= weight;
                }
            }
        }

        BigInteger fingerprint = new BigInteger("0");
        StringBuffer simHashBuffer = new StringBuffer();
        // 合并,降维
        for (int i = 0; i < this.bitLength; i++) {
            // 最后对数组进行判断,大于0的记为1,小于等于0的记为0,得到一个 64bit 的数字指纹
            if (hashValue[i] >= 0) {
                fingerprint = fingerprint.add(new BigInteger("1").shiftLeft(i));
                simHashBuffer.append("1");
            } else {
                simHashBuffer.append("0");
            }
        }

        this.strSimHash = simHashBuffer.toString();
        this.intSimHash = fingerprint;
        List<String> simHashList = new ArrayList<String>();
        simHashList.add(simHashBuffer.substring(0,this.bitLength/4));
        simHashList.add(simHashBuffer.substring(this.bitLength/4,this.bitLength/4*2));
        simHashList.add(simHashBuffer.substring(this.bitLength/4*2,this.bitLength/4*3));
        simHashList.add(simHashBuffer.substring(this.bitLength/4*3,this.bitLength));
        this.shortSimHashList = simHashList;

        return simHashList;
    }

    /**
     * 计算每个词的hash
     * @date: 2019/2/20
     * @param source
     * @return java.math.BigInteger
     * @throws 
     */
    private BigInteger hash(String source) {
        if (null == source || source.length() == 0) {
            return new BigInteger("0");
        } else {
            char[] sourceArray = source.toCharArray();
            BigInteger x = BigInteger.valueOf(((long) sourceArray[0]) << 7);
            BigInteger m = new BigInteger("1000003");
            BigInteger mask = new BigInteger("2").pow(this.bitLength).subtract(new BigInteger("1"));
            for (char item : sourceArray) {
                BigInteger temp = BigInteger.valueOf((long) item);
                x = x.multiply(m).xor(temp).and(mask);
            }
            x = x.xor(new BigInteger(String.valueOf(source.length())));
            if (x.equals(new BigInteger("-1"))) {
                x = new BigInteger("-2");
            }
            return x;
        }
    }

    /**
     * 计算hamming距离
     * @date: 2019/2/20
     * @param bigInteger1
     * @param bigInteger2
     * @return int
     * @throws 
     */
    public int hammingDistance(BigInteger bigInteger1,BigInteger bigInteger2) {
        BigInteger x = bigInteger1.xor(bigInteger2);
        int tot = 0;
        // 统计x中二进制位数为1的个数
        // 我们想想,一个二进制数减去1,那么,从最后那个1(包括那个1)后面的数字全都反了,对吧,然后,n&(n-1)就相当于把后面的数字清0,
        // 我们看n能做多少次这样的操作就OK了。
        while (x.signum() != 0) {
            tot += 1;
            x = x.and(x.subtract(new BigInteger("1")));
        }
        return tot;
    }

    /**
     * 计算二进制的hamming距离
     * @date: 2019/2/20
     * @param str1
     * @param str2
     * @return int
     * @throws 
     */
    public int hammingDistance(String str1, String str2) {
        int distance;
        if (str1.length() != str2.length()) {
            distance = -1;
        } else {
            distance = 0;
            for (int i = 0; i < str1.length(); i++) {
                if (str1.charAt(i) != str2.charAt(i)) {
                    distance++;
                }
            }
        }
        return distance;
    }

    /**
     * 根据二进制simHash 获取 intSimHash
     * @date: 2019/2/20
     * @param strSimHash
     * @return java.math.BigInteger
     * @throws
     */
    public BigInteger getIntSimHash(String strSimHash){
        BigInteger fingerprint = new BigInteger("0");
        StringBuffer simHashBuffer = new StringBuffer();
        char[] hashValue = strSimHash.toCharArray();
        for (int i = 0; i < this.bitLength; i++) {
            if (hashValue[i] == '1') {
                fingerprint = fingerprint.add(new BigInteger("1").shiftLeft(i));
            }
        }
        return fingerprint;
    }

}

猜你喜欢

转载自blog.csdn.net/ItRuler/article/details/87804208
今日推荐