java字符串比较

应该场景:
有一批酒店的产品名字,名字不规则,有中文有英文也会有特殊符号,现需要按这个产品的名称将其对应到相应的房型上。这时就需要按字符进行比较。去匹配相似度最高的房型名称之上。经过对数据的分析,最后有中文的名称采用分词的方法进行相似对比,英文的文本之间的相似度计算用的是余弦距离,先哈希过。下面是计算两个List的余弦距离。

英文字符进行相似度比较

package com.e100.hotelcore.stringUtils;
import java.util.ArrayList;  
import java.util.HashMap;  
import java.util.Iterator;  
import java.util.Map;  
  
  
public class EnStringCompare {  
  
    public static double getSimilarity(ArrayList<String> doc1, ArrayList<String> doc2) {  
        if (doc1 != null && doc1.size() > 0 && doc2 != null && doc2.size() > 0) {  
  
            Map<Long, int[]> AlgorithmMap = new HashMap<Long, int[]>();  
  
            for (int i = 0; i < doc1.size(); i++) {  
                String d1 = doc1.get(i);  
                long sIndex = hashId(d1);  
                int[] fq = AlgorithmMap.get(sIndex);  
                if (fq != null) {  
                    fq[0]++;  
                } else {  
                    fq = new int[2];  
                    fq[0] = 1;  
                    fq[1] = 0;  
                    AlgorithmMap.put(sIndex, fq);  
                }  
            }  
  
            for (int i = 0; i < doc2.size(); i++) {  
                String d2 = doc2.get(i);  
                long sIndex = hashId(d2);  
                int[] fq = AlgorithmMap.get(sIndex);  
                if (fq != null) {  
                    fq[1]++;  
                } else {  
                    fq = new int[2];  
                    fq[0] = 0;  
                    fq[1] = 1;  
                    AlgorithmMap.put(sIndex, fq);  
                }  
  
            }  
  
            Iterator<Long> iterator = AlgorithmMap.keySet().iterator();  
            double sqdoc1 = 0;  
            double sqdoc2 = 0;  
            double denominator = 0;  
            while (iterator.hasNext()) {  
                int[] c = AlgorithmMap.get(iterator.next());  
                denominator += c[0] * c[1];  
                sqdoc1 += c[0] * c[0];  
                sqdoc2 += c[1] * c[1];  
            }  
  
            return denominator / Math.sqrt(sqdoc1 * sqdoc2);  
        } else {  
            return 0;  
        }  
    }  
  
    public static long hashId(String s) {  
        long seed = 131; // 31 131 1313 13131 131313 etc.. BKDRHash   
        long hash = 0;  
        for (int i = 0; i < s.length(); i++) {  
            hash = (hash * seed) + s.charAt(i);  
        }  
        return hash;  
    }  
  
    public static void main(String[] args) {  
        ArrayList<String> t1 = new ArrayList<String>();  
        ArrayList<String> t2 = new ArrayList<String>(); 
        ArrayList<String> t3 = new ArrayList<String>(); 
        t1.add("double");  
        t1.add("or");
        t1.add("twin");
        t1.add("superior");
       
  
        t2.add("superior"); 
        t2.add("twin"); 
        t2.add("double"); 
         
        t3.add("superior");
        t3.add("suite");
        t3.add("standard");
        t3.add("zone");
        System.out.println(getSimilarity(t1, t2));
        System.out.println(getSimilarity(t1, t3));
    }  
}  

余弦相似度计算字符串相似率

比较中文比较准确,英文区分大少写,按空格分词,空格的多少也会影响到相似度,所以上面的方法比较英文比较准确。

1、pom.xml

展示一些主要的jar包

	<!--结合操作工具包-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.5</version>
        </dependency>
       <!--bean实体注解工具包-->
           <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
      <!--汉语言包,主要用于分词-->
        <dependency>
            <groupId>com.hankcs</groupId>
            <artifactId>hanlp</artifactId>
            <version>portable-1.6.5</version>
        </dependency>
2、main方法
 public static void main(String[] args) {

    	String content1 = "run of the house";

    	String content2 = "1 bed presidential suite space zone";
    	String content3 = "Premier";
    	String content4 = "Superior";
    	
		double score = CosineSimilarity.getSimilarity(content1, content2);
		System.out.println("相似度:" + score);

		score = CosineSimilarity.getSimilarity(content1, content3);
		System.out.println("相似度:" + score);
		score = CosineSimilarity.getSimilarity(content1, content4);
		System.out.println("相似度:" + score);
	}
3、Tokenizer(分词工具类)
	package com.e100.hotelcore.stringUtils;

import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;
import java.util.List;
import java.util.stream.Collectors;


/**
 * 中文分词工具类*/
public class Tokenizer {
	 /**
     * 分词*/
    public static List<Word> segment(String sentence) {

        //1、 采用HanLP中文自然语言处理中标准分词进行分词
        List<Term> termList = HanLP.segment(sentence);

        //上面控制台打印信息就是这里输出的
        System.out.println(termList.toString());

        //2、重新封装到Word对象中(term.word代表分词后的词语,term.nature代表改词的词性)
        return termList.stream().map(term -> new Word(term.word, term.nature.toString())).collect(Collectors.toList());
    }
}

4、Word(封装分词结果)
package com.e100.hotelcore.stringUtils;
import lombok.Data;

import java.util.Objects;

/**
 * 封装分词结果*/
@Data
public class Word implements Comparable {
	
	 // 词名
    private String name;
    public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	// 词性
    private String pos;

    // 权重,用于词向量分析
    private Float weight;

    public Word(String name, String pos) {
        this.name = name;
        this.pos = pos;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(this.name);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Word other = (Word) obj;
        return Objects.equals(this.name, other.name);
    }

    @Override
    public String toString() {
        StringBuilder str = new StringBuilder();
        if (name != null) {
            str.append(name);
        }
        if (pos != null) {
            str.append("/").append(pos);
        }

        return str.toString();
    }

    @Override
    public int compareTo(Object o) {
        if (this == o) {
            return 0;
        }
        if (this.name == null) {
            return -1;
        }
        if (o == null) {
            return 1;
        }
        if (!(o instanceof Word)) {
            return 1;
        }
        String t = ((Word) o).getName();
        if (t == null) {
            return 1;
        }
        return this.name.compareTo(t);
    }

	public Float getWeight() {
		return weight;
	}

	public void setWeight(Float weight) {
		this.weight = weight;
	}
	
}

5、CosineSimilarity
package com.e100.hotelcore.stringUtils;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * 判定方式:余弦相似度,通过计算两个向量的夹角余弦值来评估他们的相似度 余弦夹角原理: 向量a=(x1,y1),向量b=(x2,y2)
 * similarity=a.b/|a|*|b| a.b=x1x2+y1y2
 * |a|=根号[(x1)^2+(y1)^2],|b|=根号[(x2)^2+(y2)^2]
 */

public class CosineSimilarity {

	protected static final Logger LOGGER = LoggerFactory.getLogger(CosineSimilarity.class);

	/**
	 * 1、计算两个字符串的相似度
	 */
	public static double getSimilarity(String text1, String text2) {

		// 如果wei空,或者字符长度为0,则代表完全相同
		if (StringUtils.isBlank(text1) && StringUtils.isBlank(text2)) {
			return 1.0;
		}
		// 如果一个为0或者空,一个不为,那说明完全不相似
		if (StringUtils.isBlank(text1) || StringUtils.isBlank(text2)) {
			return 0.0;
		}
		// 这个代表如果两个字符串相等那当然返回1了(这个我为了让它也分词计算一下,所以注释掉了)
//            if (text1.equalsIgnoreCase(text2)) {
//                return 1.0;
//            }
		// 第一步:进行分词
		List<Word> words1 = Tokenizer.segment(text1);
		List<Word> words2 = Tokenizer.segment(text2);

		return getSimilarity(words1, words2);
	}

	/**
	 * 2、对于计算出的相似度保留小数点后六位
	 */
	public static double getSimilarity(List<Word> words1, List<Word> words2) {

		double score = getSimilarityImpl(words1, words2);

		// (int) (score * 1000000 + 0.5)其实代表保留小数点后六位
		// ,因为1034234.213强制转换不就是1034234。对于强制转换添加0.5就等于四舍五入
		score = (int) (score * 1000000 + 0.5) / (double) 1000000;

		return score;
	}
	
	/**
     * 文本相似度计算 判定方式:余弦相似度,通过计算两个向量的夹角余弦值来评估他们的相似度 余弦夹角原理: 向量a=(x1,y1),向量b=(x2,y2) similarity=a.b/|a|*|b| a.b=x1x2+y1y2
     * |a|=根号[(x1)^2+(y1)^2],|b|=根号[(x2)^2+(y2)^2]
     */
    public static double getSimilarityImpl(List<Word> words1, List<Word> words2) {

        // 向每一个Word对象的属性都注入weight(权重)属性值
        taggingWeightByFrequency(words1, words2);

        //第二步:计算词频
        //通过上一步让每个Word对象都有权重值,那么在封装到map中(key是词,value是该词出现的次数(即权重))
        Map<String, Float> weightMap1 = getFastSearchMap(words1);
        Map<String, Float> weightMap2 = getFastSearchMap(words2);

        //将所有词都装入set容器中
        Set<Word> words = new HashSet<>();
        words.addAll(words1);
        words.addAll(words2);

        AtomicFloat ab = new AtomicFloat();// a.b
        AtomicFloat aa = new AtomicFloat();// |a|的平方
        AtomicFloat bb = new AtomicFloat();// |b|的平方

        // 第三步:写出词频向量,后进行计算
        words.parallelStream().forEach(word -> {
            //看同一词在a、b两个集合出现的此次
            Float x1 = weightMap1.get(word.getName());
            Float x2 = weightMap2.get(word.getName());
            if (x1 != null && x2 != null) {
                //x1x2
                float oneOfTheDimension = x1 * x2;
                //+
                ab.addAndGet(oneOfTheDimension);
            }
            if (x1 != null) {
                //(x1)^2
                float oneOfTheDimension = x1 * x1;
                //+
                aa.addAndGet(oneOfTheDimension);
            }
            if (x2 != null) {
                //(x2)^2
                float oneOfTheDimension = x2 * x2;
                //+
                bb.addAndGet(oneOfTheDimension);
            }
        });
        //|a| 对aa开方
        double aaa = Math.sqrt(aa.doubleValue());
        //|b| 对bb开方
        double bbb = Math.sqrt(bb.doubleValue());

        //使用BigDecimal保证精确计算浮点数
        //double aabb = aaa * bbb;
        BigDecimal aabb = BigDecimal.valueOf(aaa).multiply(BigDecimal.valueOf(bbb));

        //similarity=a.b/|a|*|b|
        //divide参数说明:aabb被除数,9表示小数点后保留9位,最后一个表示用标准的四舍五入法
        double cos = BigDecimal.valueOf(ab.get()).divide(aabb, 9, BigDecimal.ROUND_HALF_UP).doubleValue();
        return cos;
    }
    
    /**
     * 向每一个Word对象的属性都注入weight(权重)属性值
     */
    protected static void taggingWeightByFrequency(List<Word> words1, List<Word> words2) {
        if (words1.get(0).getWeight() != null && words2.get(0).getWeight() != null) {
            return;
        }
        //词频统计(key是词,value是该词在这段句子中出现的次数)
        Map<String, AtomicInteger> frequency1 = getFrequency(words1);
        Map<String, AtomicInteger> frequency2 = getFrequency(words2);

        //如果是DEBUG模式输出词频统计信息
//        if (LOGGER.isDebugEnabled()) {
//            LOGGER.debug("词频统计1:\n{}", getWordsFrequencyString(frequency1));
//            LOGGER.debug("词频统计2:\n{}", getWordsFrequencyString(frequency2));
//        }
        // 标注权重(该词出现的次数)
        words1.parallelStream().forEach(word -> word.setWeight(frequency1.get(word.getName()).floatValue()));
        words2.parallelStream().forEach(word -> word.setWeight(frequency2.get(word.getName()).floatValue()));
    }
    /**
     * 统计词频
     * @return 词频统计图
     */
    private static Map<String, AtomicInteger> getFrequency(List<Word> words) {

        Map<String, AtomicInteger> freq = new HashMap<>();
        //这步很帅哦
        words.forEach(i -> freq.computeIfAbsent(i.getName(), k -> new AtomicInteger()).incrementAndGet());
        return freq;
    }
    /**
     * 输出:词频统计信息
     */
    private static String getWordsFrequencyString(Map<String, AtomicInteger> frequency) {
        StringBuilder str = new StringBuilder();
        if (frequency != null && !frequency.isEmpty()) {
            AtomicInteger integer = new AtomicInteger();
            frequency.entrySet().stream().sorted((a, b) -> b.getValue().get() - a.getValue().get()).forEach(
                    i -> str.append("\t").append(integer.incrementAndGet()).append("、").append(i.getKey()).append("=")
                            .append(i.getValue()).append("\n"));
        }
        str.setLength(str.length() - 1);
        return str.toString();
    }
    /**
     * 构造权重快速搜索容器
     */
    protected static Map<String, Float> getFastSearchMap(List<Word> words) {
        if (CollectionUtils.isEmpty(words)) {
            return Collections.emptyMap();
        }
        Map<String, Float> weightMap = new ConcurrentHashMap<>(words.size());

        words.parallelStream().forEach(i -> {
            if (i.getWeight() != null) {
                weightMap.put(i.getName(), i.getWeight());
            } else {
                LOGGER.error("no word weight info:" + i.getName());
            }
        });
        return weightMap;
    }
}

6、AtomicFloat原子类
package com.e100.hotelcore.stringUtils;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicFloat extends Number{
	private AtomicInteger bits;

    public AtomicFloat() {
        this(0f);
    }

    public AtomicFloat(float initialValue) {
        bits = new AtomicInteger(Float.floatToIntBits(initialValue));
    }

    //叠加
    public final float addAndGet(float delta) {
        float expect;
        float update;
        do {
            expect = get();
            update = expect + delta;
        } while (!this.compareAndSet(expect, update));

        return update;
    }

    public final float getAndAdd(float delta) {
        float expect;
        float update;
        do {
            expect = get();
            update = expect + delta;
        } while (!this.compareAndSet(expect, update));

        return expect;
    }

    public final float getAndDecrement() {
        return getAndAdd(-1);
    }

    public final float decrementAndGet() {
        return addAndGet(-1);
    }

    public final float getAndIncrement() {
        return getAndAdd(1);
    }

    public final float incrementAndGet() {
        return addAndGet(1);
    }

    public final float getAndSet(float newValue) {
        float expect;
        do {
            expect = get();
        } while (!this.compareAndSet(expect, newValue));

        return expect;
    }

    public final boolean compareAndSet(float expect, float update) {
        return bits.compareAndSet(Float.floatToIntBits(expect), Float.floatToIntBits(update));
    }

    public final void set(float newValue) {
        bits.set(Float.floatToIntBits(newValue));
    }

    public final float get() {
        return Float.intBitsToFloat(bits.get());
    }

    @Override
    public float floatValue() {
        return get();
    }

    @Override
    public double doubleValue() {
        return (double) floatValue();
    }

    @Override
    public int intValue() {
        return (int) get();
    }

    @Override
    public long longValue() {
        return (long) get();
    }

    @Override
    public String toString() {
        return Float.toString(get());
    }
}

7、总结

把大致思路再捋一下:

(1)先分词: 分词当然要按一定规则,不然随便分那也没有意义,那这里通过采用HanLP中文自然语言处理中标准分词进行分词。

(2)统计词频: 就统计上面词出现的次数。

(3)通过每一个词出现的次数,变成一个向量,通过向量公式计算相似率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java字符串比较可以使用多种方法,其中最常用的是equals()方法。该方法比较两个字符串的内容是否相同,而不是比较它们的引用是否相同。因此,即使两个字符串对象的引用不同,只要它们的内容相同,equals()方法就会返回true。 以下是Java字符串比较的三种方法: 1.使用equals()方法比较字符串内容是否相同。 ```java String str1 = "hello"; String str2 = "world"; if(str1.equals(str2)){ System.out.println("str1和str2的内容相同");}else{ System.out.println("str1和str2的内容不同"); } ``` 2.使用compareTo()方法比较字符串的字典顺序。该方法返回一个整数,如果字符串在字典中排在参数字符串之前,则返回负数;如果字符串在字典中排在参数字符串之后,则返回正数;如果两个字符串相等,则返回0。 ```java String str1 = "hello"; String str2 = "world"; int result = str1.compareTo(str2); if(result < 0){ System.out.println("str1在字典中排在str2之前"); }else if(result > 0){ System.out.println("str1在字典中排在str2之后"); }else{ System.out.println("str1和str2在字典中的位置相同"); } ``` 3.使用==运算符比较两个字符串的引用是否相同。如果两个字符串的引用指向同一个对象,则返回true;否则返回false。 ```java String str1 = "hello"; String str2 = "hello"; if(str1 == str2){ System.out.println("str1和str2的引用相同"); }else{ System.out.println("str1和str2的引用不同"); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值