zoukankan      html  css  js  c++  java
  • 关于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%以下(因为根本很明显不是相似的),这与预期是相同的。

  • 相关阅读:
    一个web程序员的年终总结
    编程要诀-心态
    初识vps,域名与购买,初步配置
    一个好的学习方法真的很重要——费曼学习法
    [译]C# 7系列,Part 8: in Parameters in参数
    Dalsa 8K彩色相机Camera link C#采图
    精简Command版SqlHelper
    ASP.NET MVC模块化开发——动态挂载外部项目
    net core WebApi——依赖注入Autofac
    .NET Core 3 WPF MVVM框架 Prism系列之命令
  • 原文地址:https://www.cnblogs.com/qijunhui/p/8448805.html
Copyright © 2011-2022 走看看