关于SimHash算法的实现及测试V2.0
@祁俊辉,2017年6月4日测试。
1 说明
- 本文章衔接关于SimHash算法的实现及测试V1.0;
- 将Hash函数更新为MD5_Hash函数(二进制为128位);
- 个人感觉用海明距离并不能只管说明两篇文章(字符串)相似,故添加相似度,但对于相似度的计算只是利用最简单的,有很多不妥之处。
2 MD5_Hash算法
2.1 MD5简介
MD5即“消息-摘要算法第五版”,是计算机领域广泛使用的一种散列函数,用以提供消息的完整性保护。
特点:
- 压缩性:任意长度的数据,算出的MD5值是长度固定的。
- 易计算:从原数据计算出MD5值很容易。
- 抗修改性:对原数据进行任何改动,所得到的MD5值都有很大区别。
- 强抗碰撞:已知原数据和其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%以下(因为根本很明显不是相似的),这与预期是相同的。