@祁俊辉,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%以下(因为根本很明显不是相似的),这与预期是相同的。
来源:oschina
链接:https://my.oschina.net/u/4319991/blog/4236297