关于SimHash算法的实现及测试V2.0

@祁俊辉,2017年6月4日测试。

1  说明

  • 本文章衔接关于SimHash算法的实现及测试V1.0
  • 将Hash函数更新为MD5_Hash函数(二进制为128位);
  • 个人感觉用海明距离并不能只管说明两篇文章(字符串)相似,故添加相似度,但对于相似度的计算只是利用最简单的,有很多不妥之处。

2  MD5_Hash算法

2.1  MD5简介

MD5即“消息-摘要算法第五版”,是计算机领域广泛使用的一种散列函数,用以提供消息的完整性保护。

特点:

  1. 压缩性:任意长度的数据,算出的MD5值是长度固定的。
  2. 易计算:从原数据计算出MD5值很容易。
  3. 抗修改性:对原数据进行任何改动,所得到的MD5值都有很大区别。
  4. 强抗碰撞:已知原数据和其MD5值,想找到一个具有相同的MD5值的数据是非常困难的。

注:加密和摘要是不一样的!

  • 加密后的消息是完整的,具有解密算法,得到原始数据。
  • 摘要得到的消息是不完整的,通过摘要的数据,不能得到原始数据。

2.2  MD5长度问题

标准的MD5算法输出的是128bit二进制串。

但有些情况下,对于128bit数据不易处理,故取4bit成一个十六进制,常用32位十六进制串表示。

网上也有16位十六进制串所表示的MD5值,是将正常的32位十六进制串的前8位和后8位去掉,中间的部分所组成的16位十六进制串。

2.3  MD5应用

  • 一致性效验。如常见的下载站点中,常用MD5值验证用户得到的软件与站点提供的软件是否一致,或是否下载过程中出现丢失数据等情况。
  • 数字签名。如将文件经MD5映射为唯一“指纹”。(注:不是绝对的“唯一”)
  • 安全访问认证。如密码在数据库的存储。

2.4  MD5原理

简要叙述:MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。
详细过程请自行百度,本文不予叙述。

2.5  MD5算法的Java实现

 1 import java.math.BigInteger;
 2 import java.security.MessageDigest;
 3 
 4 public class MD5 {
 5     
 6     public static void main(String[] args) {
 7         String md5 = getMD5("中国");
 8         System.out.println(md5);
 9         String md6 = getMD5("SimHash");
10         System.out.println(md6);
11     }
12     
13     public static String getMD5(String str) {
14         try{
15             //生成一个MD5加密计算摘要
16             MessageDigest md = MessageDigest.getInstance("MD5");
17             //计算md5函数
18             //md.update(str.getBytes());
19             //System.out.println("字符串:"+str);
20             //System.out.println("字符串的MD5_Hash:"+md.digest(str.getBytes()));
21             //digest()最后确定返回MD5_Hash值,返回值为8为字符串。因为MD5_Hash值是16位的hex值,实际上就是8位的字符
22             //BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值
23             //toString中可自由将返回值变换为十六进制或二进制串
24             return new BigInteger(1,md.digest(str.getBytes("UTF-8"))).toString(16);
25         }catch(Exception e){
26             e.printStackTrace();
27             return str;
28         }
29     }
30 }

注:中文字符必须使用“UTF-8”编码,否则会出错。

3  程序

  1 import java.math.BigInteger;
  2 import java.security.MessageDigest;
  3 
  4 /* 【算法】SimHash->128位
  5  * 【说明】1.本程序手动分词,假设每个词的权重都为1
  6  * 【说明】2.对每个词进行MD5Hash算法,在此基础上加减权重
  7  * 【说明】3.将所有词整合后,降维
  8  * 【说明】4.计算各个句子的海明距离
  9  * 【时间】祁俊辉->2017.6.2
 10  * */
 11 public class SimHash_128 {
 12     //定义待比较的字符串
 13     static String s1="SimHash/算法/的/研究";
 14     static String s2="SimHash/算法/的/探讨";
 15     static String s3="SimHash/研究/的/算法";
 16     static String s4="SimHash/是/一种/文本/相似性/算法";
 17     //定义待比较的字符串
 18     static String s5="电视剧/小时代/由/郭敬明/的/同名/小说/改编/而/成/故事/以/经济/飞速/发展/的/上海/这/座/风光/而/时尚/的/城市/为/背景/讲述/了/林萧/南湘/顾里/唐宛如/这/四/个/从小/感情/深厚/有着/不同/价值观/和/人生观/的/女生/先后/所/经历/的/友情/爱情/乃至/亲情/的/巨大/转变/是/一/部/当下/年轻人/生活/一个/侧面/的/真实/写照";
 19     static String s6="电视剧/大时代/由/郭敬明/的/同名/小说/改编/而/成/该剧情/以/经济/飞速/发展/的/大上海/这/座/风光/而/时尚/的/城市/为/背景/讲述/了/林萧/南湘/顾里/唐宛如/这/四/个/从小/感情/深厚/有着/不同/价值观/和/人生观/的/女生/先后/所/经历/的/友情/爱情/乃至/亲情/的/巨大/转变/是/一/部/当下/年轻人/生活/一个/侧面/的/真实/写照";
 20     //定义待比较的字符串
 21     static String s7="你/妈妈/喊/你/回家/吃饭/哦/回家/喽/回家/喽";
 22     static String s8="你/妈妈/叫/你/回家/吃饭/哦/回家/喽/回家/喽";
 23     /* 函数名:MD5_Hash(String str)
 24      * 功能:计算字符串str的128位hash值,并将其以String型返回
 25      * */
 26     static String MD5_Hash(String str){
 27         try{
 28             // 生成一个MD5加密计算摘要
 29             MessageDigest md = MessageDigest.getInstance("MD5");
 30             // 计算md5函数
 31             //System.out.println("字符串:"+str);
 32             //System.out.println("字符串的MD5_Hash:"+md.digest(str.getBytes()));
 33             // digest()最后确定返回md5 hash值,返回值为8为字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符
 34             // BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值
 35             return new BigInteger(1,md.digest(str.getBytes("UTF-8"))).toString(2);
 36         }catch(Exception e){
 37             e.printStackTrace();
 38             return str;
 39         }
 40     }
 41     /* 函数名:First_FC(String str)
 42      * 功能:1.先创建一个存储SimHash值的128数组,并初始化为0
 43      * 功能:2.将str字符串分词,并存入临时数组
 44      * 功能:3.计算此字符串的SimHash值(加权、但没有降维),存储在数组中
 45      * 功能:4.将数组中的SimHash值降维,并以字符串形式返回
 46      * */
 47     static String First_FC(String str){
 48         //1.先创建一个存储SimHash值的128数组,并初始化为0
 49         int Hash_SZ[] = new int[128];
 50         for(int i=0;i<Hash_SZ.length;i++)
 51             Hash_SZ[i]=0;
 52         //2.将str字符串分词,并存入临时数组
 53         String[] newstr = str.split("/");
 54         //下面的for循环是为了增加前后文关联性//即12/23/34/45···
 55         /*for(int i=0;i<newstr.length-1;i++){
 56             newstr[i]=newstr[i]+newstr[i+1];
 57         }*/
 58         //相应的,若增加上面的关联性语句,下面一行的代码要改为
 59         //for(int i=0;i<newstr.length-1;i++){
 60         //3.计算此字符串的SimHash值(加权、但没有降维),存储在数组中
 61         for(int i=0;i<newstr.length;i++){//循环传入字符串的每个词
 62             String str_hash=MD5_Hash(newstr[i]);//先计算每一个词的Hash值(128位)
 63             //MD5哈希计算时,二进制转换若最高位为7以下,也就是转换成二进制最高位为0,会不存储该0,导致后面程序出错
 64             //这里主要是为了保证它是128位的二进制
 65             if(str_hash.length() < 128){
 66                 int que = 128 - str_hash.length();
 67                 for(int j=0;j<que;j++){
 68                     str_hash = "0" + str_hash;
 69                 }
 70             }
 71             //System.out.println(str_hash);//输出该词的128位MD5哈希值
 72             char str_hash_fb[]=str_hash.toCharArray();//将该词的哈希值转为数组,方便检查
 73             //System.out.println(Integer.toBinaryString(str_hash));
 74             //对每个词的Hash值(32位)求j位是1还是0,1的话加上该词的权重,0的话减去该词的权重
 75             for(int j=0;j<Hash_SZ.length;j++){
 76                 if(str_hash_fb[j] == '1'){
 77                     Hash_SZ[j]++;//Hash_SZ中,0是最高位,依次排低
 78                 }else{
 79                     Hash_SZ[j]--;
 80                 }
 81             }
 82         }
 83         //4.将数组中的SimHash值降维,并以字符串形式返回
 84         String SimHash_number="";//存储SimHash值
 85         for(int i=0;i<Hash_SZ.length;i++){//从高位到低位
 86             System.out.print(Hash_SZ[i]+" ");//输出未降维的串
 87             if(Hash_SZ[i]<=0)//小于等于0,就取0
 88                 SimHash_number += "0";
 89             else//大于0,就取1
 90                 SimHash_number += "1";
 91         }
 92         System.out.println("");//换行
 93         return SimHash_number;
 94     }
 95     /* 函数名:HMJL(String a,String b)
 96      * 功能:a、b都是以String存储的二进制数,计算他们的海明距离,并将其返回
 97      * */
 98     static int HMJL(String a,String b){
 99         char[] FW1 = a.toCharArray();//将a每一位都存入数组中
100         char[] FW2 = b.toCharArray();//将b每一位都存入数组中
101         int haiming=0;
102         if(FW1.length == FW2.length){//确保a和b的位数是相同的
103             for(int i=0;i<FW1.length;i++){
104                 if(FW1[i] != FW2[i])//如果该位不同,海明距离加1
105                     haiming++;
106             }
107         }
108         return haiming;
109     }
110     
111     public static void main(String[] args) {
112         String a1 = First_FC(s1);
113         String a2 = First_FC(s2);
114         String a3 = First_FC(s3);
115         String a4 = First_FC(s4);
116         System.out.println("【s1】的SimHash值为:"+a1);
117         System.out.println("【s2】的SimHash值为:"+a2);
118         System.out.println("【s3】的SimHash值为:"+a3);
119         System.out.println("【s4】的SimHash值为:"+a4);
120         System.out.println("【s1】和【s2】的海明距离为:"+HMJL(a1,a2)+",相似度为:"+(100-HMJL(a1,a2)*100/128)+"%");
121         System.out.println("【s1】和【s3】的海明距离为:"+HMJL(a1,a3)+",相似度为:"+(100-HMJL(a1,a3)*100/128)+"%");
122         System.out.println("【s1】和【s4】的海明距离为:"+HMJL(a1,a4)+",相似度为:"+(100-HMJL(a1,a4)*100/128)+"%");
123         System.out.println("【s2】和【s3】的海明距离为:"+HMJL(a2,a3)+",相似度为:"+(100-HMJL(a2,a3)*100/128)+"%");
124         System.out.println("【s2】和【s4】的海明距离为:"+HMJL(a2,a4)+",相似度为:"+(100-HMJL(a2,a4)*100/128)+"%");
125         System.out.println("【s3】和【s4】的海明距离为:"+HMJL(a3,a4)+",相似度为:"+(100-HMJL(a3,a4)*100/128)+"%");
126         
127         String a5 = First_FC(s5);
128         String a6 = First_FC(s6);
129         String a7 = First_FC(s7);
130         String a8 = First_FC(s8);
131         System.out.println("【s5】的SimHash值为:"+a5);
132         System.out.println("【s6】的SimHash值为:"+a6);
133         System.out.println("【s7】的SimHash值为:"+a7);
134         System.out.println("【s8】的SimHash值为:"+a8);
135         System.out.println("【s5】和【s6】的海明距离为:"+HMJL(a5,a6)+",相似度为:"+(100-HMJL(a5,a6)*100/128)+"%");
136         System.out.println("【s5】和【s7】的海明距离为:"+HMJL(a5,a7)+",相似度为:"+(100-HMJL(a5,a7)*100/128)+"%");
137         System.out.println("【s5】和【s8】的海明距离为:"+HMJL(a5,a8)+",相似度为:"+(100-HMJL(a5,a8)*100/128)+"%");
138         System.out.println("【s6】和【s7】的海明距离为:"+HMJL(a6,a7)+",相似度为:"+(100-HMJL(a6,a7)*100/128)+"%");
139         System.out.println("【s6】和【s8】的海明距离为:"+HMJL(a6,a8)+",相似度为:"+(100-HMJL(a6,a8)*100/128)+"%");
140         System.out.println("【s7】和【s8】的海明距离为:"+HMJL(a7,a8)+",相似度为:"+(100-HMJL(a7,a8)*100/128)+"%");
141     }
142 }

4  测试结果

4.1  第一个测试A1

测试时使用短字符串,对每个词使用的是128位MD5_Hash算法,此次测试并未考虑上下文相关性,四个字符串分别为:

  • S1:SimHash/算法/的/研究
  • S2:SimHash/算法/的/探讨
  • S3:SimHash/研究/的/算法
  • S4:SimHash/是/一种/文本/相似性/算法

未降维时四个字符串的SimHash值为(太长,截取了一部分):

降维后的SimHash值为(太长,截取了一部分):

计算各个字符串之间的海明距离:

结果与关于SimHash算法的实现及测试V1.0中的测试结果几乎相同,但更为明显,且增加了相似度的计算。

这里的相似度计算是用“(128-海明距离)/128”得到的,对于这个公式个人感觉是不正确的,但是目前还没有想到更好的办法,后期将会改正。

4.2  第二个测试A2

测试时使用中等长度字符串,对每个词使用的是128位MD5_Hash算法,此次测试并未考虑上下文相关性,四个字符串分别为:

  • S5:电视剧/小时代/由/郭敬明/的/同名/小说/改编/而/成/故事/以/经济/飞速/发展/的/上海/这/座/风光/而/时尚/的/城市/为/背景/讲述/了/林萧/南湘/顾里/唐宛如/这/四/个/从小/感情/深厚/有着/不同/价值观/和/人生观/的/女生/先后/所/经历/的/友情/爱情/乃至/亲情/的/巨大/转变/是/一/部/当下/年轻人/生活/一个/侧面/的/真实/写照
  • S6:电视剧/大时代/由/郭敬明/的/同名/小说/改编/而/成/该剧情/以/经济/飞速/发展/的/大上海/这/座/风光/而/时尚/的/城市/为/背景/讲述/了/林萧/南湘/顾里/唐宛如/这/四/个/从小/感情/深厚/有着/不同/价值观/和/人生观/的/女生/先后/所/经历/的/友情/爱情/乃至/亲情/的/巨大/转变/是/一/部/当下/年轻人/生活/一个/侧面/的/真实/写照
  • S7:你/妈妈/喊/你/回家/吃饭/哦/回家/喽/回家/喽
  • S8:你/妈妈/叫/你/回家/吃饭/哦/回家/喽/回家/喽

未降维时四个字符串的SimHash值为(太长,截取了一部分):

降维后的SimHash值为(太长,截取了一部分):

计算各个字符串之间的海明距离:

结果与关于SimHash算法的实现及测试V1.0中的测试结果几乎相同,但更为明显,且增加了相似度的计算。

可以看出,S5和S6、S7和S8之间的相似度都在90%以上,几乎可以断定两个字符串是相似的,确实他们本身也相似。而其他情况的相似度都在50%以下(因为根本很明显不是相似的),这与预期是相同的。

posted @ 2018-02-14 20:14  祁俊辉  阅读(1399)  评论(0编辑  收藏  举报