SimHash实现的评论查重

simhash算法分为5个步骤:分词、hash、加权、合并、降维,具体过程如下所述:

  • 分词
    • 给定一段语句,进行分词,得到有效的特征向量,然后为每一个特征向量设置1-5等5个级别的权重(如果是给定一个文本,那么特征向量可以是文本中的词,其权重可以是这个词出现的次数)。例如给定一段语句:“CSDN博客结构之法算法之道的作者July”,分词后为:“CSDN 博客 结构 之 法 算法 之 道 的 作者 July”,然后为每个特征向量赋予权值:CSDN(4) 博客(5) 结构(3) 之(1) 法(2) 算法(3) 之(1) 道(2) 的(1) 作者(5) July(5),其中括号里的数字代表这个单词在整条语句中的重要程度,数字越大代表越重要。
  • hash
    • 通过hash函数计算各个特征向量的hash值,hash值为二进制数01组成的n-bit签名。比如“CSDN”的hash值Hash(CSDN)为100101,“博客”的hash值Hash(博客)为“101011”。就这样,字符串就变成了一系列数字。
  • 加权
    • 在hash值的基础上,给所有特征向量进行加权,即W = Hash * weight,且遇到1则hash值和权值正相乘,遇到0则hash值和权值负相乘。例如给“CSDN”的hash值“100101”加权得到:W(CSDN) = 100101 4 = 4 -4 -4 4 -4 4,给“博客”的hash值“101011”加权得到:W(博客)=101011 5 = 5 -5 5 -5 5 5,其余特征向量类似此般操作。
  • 合并
    • 将上述各个特征向量的加权结果累加,变成只有一个序列串。拿前两个特征向量举例,例如“CSDN”的“4 -4 -4 4 -4 4”和“博客”的“5 -5 5 -5 5 5”进行累加,得到“4+5 -4+-5 -4+5 4+-5 -4+5 4+5”,得到“9 -9 1 -1 1”。
  • 降维
    • 对于n-bit签名的累加结果,如果大于0则置1,否则置0,从而得到该语句的simhash值,最后我们便可以根据不同语句simhash的海明距离来判断它们的相似度。例如把上面计算出来的“9 -9 1 -1 1 9”降维(某位大于0记为1,小于0记为0),得到的01串为:“1 0 1 0 1 1”,从而形成它们的simhash签名。


计算差异度:

将两文本的simhash进行异或,生成的simhash中1的位数极为差异度。

 

simhash存储:以64位simhash为例。

最大个数2^64;平均查询次数2^63

最大占用内存:2^64*(64*2+12)+24    12为char[]对象头 ,24为 8个字节的String字节头、4个字节的char数组引用,3个int变量(offset、hash、count)

采用索引 hashmap>,如下图


平局查询 常数 O(1)

最大占用内存 4*2^16(16*2+12)+24;

注意:索引的个数一般为差异度+1(这样能确保至少有一个索引相同)

          simhash%索引个数==0;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.HashMultimap;

import java.math.BigInteger;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class SimHash {

    private String tokens;

    private BigInteger intSimHash;

    private String strSimHash;
    private static volatile HashMultimap characters = HashMultimap.create();
    private int hashbits = 64;

    public SimHash(String tokens) {
        this.tokens = tokens;
        this.intSimHash = this.simHash();
    }

    public SimHash(String tokens, int hashbits) {
        this.tokens = tokens;
        this.hashbits = hashbits;
        this.intSimHash = this.simHash();
    }

    public BigInteger simHash() {
        int[] v = new int[this.hashbits];
        // 分词算法按需选择
        char[] chars = this.tokens.toCharArray();
        for (char ch : chars) {
            BigInteger t = this.hash(String.valueOf(ch));
            BigInteger bitmask = new BigInteger("1");
            // 加权合并
            for (int i = 0; i < this.hashbits; i++) {
                bitmask = bitmask.shiftLeft(i);
                if (t.and(bitmask).signum() != 0) {
                    v[i] += 1;
                } else {
                    v[i] -= 1;
                }
            }
        }
        BigInteger fingerprint = new BigInteger("0");
        StringBuffer simHashBuffer = new StringBuffer();
        for (int i = 0; i < this.hashbits; i++) {
            if (v[i] >= 0) {
                fingerprint = fingerprint.add(new BigInteger("1").shiftLeft(i));
                simHashBuffer.append("1");
            } else {
                simHashBuffer.append("0");
            }
        }
        this.strSimHash = simHashBuffer.toString();
        return fingerprint;
    }

    private BigInteger hash(String source) {
        if (source == null || 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.hashbits).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;
        }
    }

    /**
     * 取两个二进制的异或,统计为1的个数,就是海明距离
     *
     * @param other
     * @return
     */

    public int hammingDistance(SimHash other) {

        BigInteger x = this.intSimHash.xor(other.intSimHash);
        int tot = 0;
        BigInteger bigInteger = new BigInteger("1");
        // 统计x中二进制位数为1的个数

        while (x.signum() != 0) {
            tot +=x.and(bigInteger).signum();
            x = x.shiftRight(1);
        }
        return tot;
    }

    /**
     * calculate Hamming Distance between two strings
     * 二进制怕有错,当成字符串,作一个,比较下结果
     *
     * @param str1
     *            the 1st string
     * @param str2
     *            the 2nd string
     * @return Hamming Distance between str1 and str2
     * @author
     */
    public static int getDistance(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;
    }

    /**
     * 如果海明距离取3,则分成四块,并得到每一块的simhash值 ,作为索引值使用
     *
     * @param simHash
     * @param numEach=(hashbits/海明距离+1)。
     * @return
     */
    // hashbits%索引个数==0
    public void storIndex(SimHash simHash, int numEach) {
        int index = 0;
        for (int i = 0; i < simHash.hashbits; i = i + numEach) {
            characters.put(index++, simHash.strSimHash.substring(i, i + numEach));
        }
    }

    public static String createObject(String key) {
        System.out.println("createObject");
        return key + "1";
    }

    public static void main(String[] args) throws ExecutionException {

        // LoadingCache cahceBuilder= CacheBuilder
        // .newBuilder()
        // .maximumSize(10)
        // .expireAfterAccess(100, TimeUnit.SECONDS)//过期时间
        // .expireAfterWrite(11,TimeUnit.SECONDS)//给定时间内没有写访问
        // .refreshAfterWrite(1,TimeUnit.SECONDS)// 给定时间没有读访问。
        // .build(new CacheLoader(){
        // @Override
        // public String load(String key) throws Exception {
        // return createObject(key);
        // }
        //
        // });
        // cahceBuilder.put("1","1");
        // cahceBuilder.refresh("1");
        // System.out.println(cahceBuilder.get("1"));
         String s = "哈哈哈";
         SimHash hash1 = new SimHash(s, 64);
         System.out.println(hash1.strSimHash);
         String s1 = "我是哈哈,";
         SimHash hash = new SimHash(s1, 64);
         System.out.println(hash.strSimHash);
         System.out.println(hash.hammingDistance(hash1));
         hash.storIndex(hash,64/4);
         hash1.storIndex(hash1,64/4);
         System.out.println(characters);
    }
}


 
  

你可能感兴趣的:(SimHash实现的评论查重)