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;

/**
 * 

SimHash 去重实现类

* * @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 shortSimHashList; public BigInteger getIntSimHash(){ return this.intSimHash; } public String getStrSimHash() { return this.strSimHash; } public List getShortSimHashList() { return shortSimHashList; } public int getBitLength() { return bitLength; } /** * 下面这两个目前没有用到,保留下来是为了后期可扩展 * * 停用的词性 */ private static Set stopNatures = Sets.newHashSet(); /** * 特殊词的权重 */ private static Map 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"," "," "}; 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 * @throws */ public List simHash(String content) { content = preProcess(content); if(StringUtils.isBlank(content)){ return Lists.newArrayList(); } // 词的权重 Map wordCount = Maps.newHashMap(); // 定义特征向量/数组 int[] hashValue = new int[this.bitLength]; // 分词:索引分词 List 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 simHashList = new ArrayList(); 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; } }

 

你可能感兴趣的:(Utils)