使用Java实现拼音模糊搜索功能(支持拼音、首字母、多音字、谐音字、汉字、阿拉伯数字)

🍎 介绍

Java实现的简单的工具类支持(拼音, 多音字, 谐音字, 汉字, 阿拉伯数字) 对标阿里钉钉的上方搜索栏实现的


🍉 对应依赖

<!-- https://mvnrepository.com/artifact/com.github.open-android/pinyin4j -->
<dependency>
    <groupId>com.github.open-android</groupId>
    <artifactId>pinyin4j</artifactId>
    <version>2.5.0</version>
</dependency>
// https://mvnrepository.com/artifact/com.github.open-android/pinyin4j
implementation group: 'com.github.open-android', name: 'pinyin4j', version: '2.5.0'

🔥 代码调试

  1. 代码中含有一个main方法, 在方法传入参数即可使用
  2. fuzzyQuery()就是模糊查询的方法, name即为用户输入的拼音, userName即为需要匹配的名称集合

 


👀 业务思路

  1. 当库中已进行租户隔离并且业务数据较少时,您可以使用工具类对库中的姓名进行模糊匹配(前提需要将库中所有的数据查询出来进行比对),并将结果全部查出, 再将结果去库中查询
  2. 当库中的数据量庞大且增长速度快时,我们建议将生成的拼音字符串存储到MySQL中,然后再进行模糊匹配操作。这样可以提高查询效率。

💰 源码

仓库地址(后期会更新) : https://gitee.com/programmer-k/pinyin-fuzzy-search

package com.itjcloud.dingdong.server.common.utils;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.pinyin4j.PinyinHelper;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 拼音模糊搜索工具类
 */
@Slf4j
public class PinyinFuzzySearchUtil {

    //汉字
    private static final int CHINESE_CHARACTER = 1;
    //拼音
    private static final int PIN_YIN = 2;
    //即包含汉字,也包含字母
    private static final int WHOLE = 3;


    //测试方法
    public static void main(String[] args) {
        System.out.println("------------------------------最终搜索结果>>>" + fuzzyQuery("zhang", Arrays.asList("刘长青1号?测试", "刘长青2号[]+-", "忘长长", "张长")));
    }


    // 将n个数组元素进行组合
    public static List<String> combineArrays(List<List<String>> arrays) {
        List<String> result = new ArrayList<>();
        // 从索引0开始组合
        combineHelper(arrays, 0, "", result);
        return result;
    }

    // 递归函数,用于将数组元素进行组合
    private static void combineHelper(List<List<String>> arrays, int index, String current, List<String> result) {
        // 当索引等于数组个数时,表示已经遍历完所有数组元素,将当前组合添加到结果列表中
        if (index == arrays.size()) {
            result.add(current);
            return;
        }

        // 获取当前数组
        List<String> currentArray = arrays.get(index);
        // 遍历当前数组的元素
        for (String word : currentArray) {
            // 递归调用,将当前元素与下一个数组进行组合
            combineHelper(arrays, index + 1, current + word, result);
        }
    }

    /**
     * 拼音转换(不含首字母)
     *
     * @param chinese 中文姓名
     * @return 拼音
     */
    public static List<String> pinYinConvertNoInitial(String chinese) {
        // 存储拼音字符串
        List<List<String>> pinyinArrays = new ArrayList<>();

        for (int i = 0; i < chinese.length(); i++) {
            char c = chinese.charAt(i);

            // 忽略空格
            if (Character.isWhitespace(c)) {
                continue;
            }

            String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c);
            log.info("pinyinArray:{}", (Object) pinyinArray);

            if (pinyinArray != null && pinyinArray.length > 0) {
                // 使用正则表达式去除声调
                List<String> pinyin = Arrays.stream(pinyinArray).map(str -> str.replaceAll("[1-5]", "")).collect(Collectors.toList());
                pinyinArrays.add(pinyin);
            }
        }

        //最终的拼音组合
        System.out.println("---------" + pinyinArrays);
        List<String> pinyinCombinations = combineArrays(pinyinArrays);
        log.info("输出拼音:{}", JSON.toJSONString(pinyinCombinations));

        return pinyinCombinations;
    }

    /**
     * 拼音转换
     *
     * @param chinese 中文姓名
     * @return 拼音和首字母
     */
    public static String pinYinConvert(String chinese) {


        // 存储拼音字符串
        List<List<String>> pinyinArrays = new ArrayList<>();
        // 存储首字母
        List<List<String>> initialArrays = new ArrayList<>();

        for (int i = 0; i < chinese.length(); i++) {
            char c = chinese.charAt(i);

            // 忽略空格
            if (Character.isWhitespace(c)) {
                continue;
            }


            String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c);
            log.info("pinyinArray:{}", (Object) pinyinArray);

            if (pinyinArray != null && pinyinArray.length > 0) {

                // 使用正则表达式去除声调
                List<String> pinyin = Arrays.stream(pinyinArray).map(str -> str.replaceAll("[1-5]", "")).collect(Collectors.toList());
                pinyinArrays.add(pinyin);

                //首字母
                List<String> initial = Arrays.stream(pinyinArray).map(str -> str.replaceAll("[1-5]", "")).map(str -> String.valueOf(str.charAt(0))).collect(Collectors.toList());
                initialArrays.add(initial);

            }
        }

        //最终的拼音组合
        List<String> pinyinCombinations = combineArrays(pinyinArrays);
        String pinyinCombinationsJoin = String.join(",", pinyinCombinations);

        //最终的首字母组合
        List<String> initialCombinations = combineArrays(initialArrays);
        String initialCombinationsJoin = String.join(",", initialCombinations);

        log.info("输出首字母,拼音:{}", initialCombinationsJoin + "," + pinyinCombinationsJoin);
        return initialCombinationsJoin + "," + pinyinCombinationsJoin;
    }



    /**
     * 汉字模糊搜索
     *
     * @param chineseCharacters 用户输入的汉字
     * @param userName          需要匹配的名称集合
     * @return 匹配成功用户名称
     */
    public static List<String> chineseCharactersFuzzySearch(String chineseCharacters, List<String> userName) {
        if (CollectionUtil.isEmpty(userName)) {
            log.info("userName为空");
            return null;
        }

        //如果用户输入的是单个字符,则按照中文模糊搜索
        if (chineseCharacters.length() == 1) {
            return userName.stream().map(name -> {
                if (name.contains(chineseCharacters)) {
                    return name;
                }
                return null;
            }).filter(Objects::nonNull).collect(Collectors.toList());
        }

        //如果用户输入的事多个字符, 则按照谐音去搜索, 既输入长三 可以搜索出张三
        if (chineseCharacters.length() > 1) {
            //将汉字转换为拼音
            List<String> pinyin = pinYinConvertNoInitial(chineseCharacters);

            //循环拼音模糊搜索, 将返回值扁平流组装为一个数组
            return pinyin.stream().flatMap(user -> Objects.requireNonNull(pinyinFuzzySearch(user, userName)).stream()).distinct().collect(Collectors.toList());
        }

        return null;
    }

    /**
     * 拼音模糊搜索
     *
     * @param pinyin   用户输入的拼音
     * @param userName 需要匹配的名称集合
     * @return 匹配成功用户名称
     */
    public static List<String> pinyinFuzzySearch(String pinyin, List<String> userName) {

        String lowercasePinYin = pinyin.toLowerCase();

        if (CollectionUtil.isEmpty(userName)) {
            log.info("userName为空");
            return null;
        }

        //存储<拼音大小写, 姓名>
        Map<String, String> pinYinMap = new HashMap<>();
        for (String name : userName) {
            //将姓名转换为 拼音首字母 + 拼音
            String pinYin = pinYinConvert(name);
            pinYinMap.put(pinYin, name);
        }

        List<String> userNameList = new ArrayList<>();

        //循环对拼音进行模糊匹配
        pinYinMap.forEach((py, name) -> {
            if (py.contains(lowercasePinYin)) {
                userNameList.add(name);
            }
        });

        return userNameList;
    }

    /**
     * 判断当前字符串的类型
     *
     * @param userName 用户名称
     * @return 1是汉字 2是字母 3既是汉字也是字母
     */
    public static int determineStringType(String userName) {
        if (userName.matches("[\\u4E00-\\u9FA5]+")) {
            log.info("字符串由汉字组成:{}", userName);
            return CHINESE_CHARACTER;
        } else if (userName.matches("[a-zA-Z]+")) {
            log.info("字符串由字母组成:{}", userName);
            return PIN_YIN;
        } else {
            log.info("字符串既包含汉字又包含字母:{}", userName);
            return WHOLE;
        }
    }

    public static String  convertToChinese(String str) {
        String pattern = ".*\\d+.*"; // 包含数字的正则表达式

        if (str.matches(pattern)) {
            str = str.replaceAll("0", "零")
                    .replaceAll("1", "一")
                    .replaceAll("2", "二")
                    .replaceAll("3", "三")
                    .replaceAll("4", "四")
                    .replaceAll("5", "五")
                    .replaceAll("6", "六")
                    .replaceAll("7", "七")
                    .replaceAll("8", "八")
                    .replaceAll("9", "九");
        }
        return str; // 返回修改后的值
    }


    public static List<String> numberToChineseCharacters(List<String> userName) {
        List<String> list = new ArrayList<>();
        for (String str : userName) {
            list.add(convertToChinese(str));
        }
        return list;
    }

    /**
     * 模糊查询
     *
     * @param name     用户输入的拼音
     * @param userName 需要匹配的名称集合
     */
    public static List<String> fuzzyQuery(String name, List<String> userName) {

        String convertName = convertToChinese(name);

        List<String> convertUserName = numberToChineseCharacters(userName);

        //判断当前字符串的类型
        int state = determineStringType(convertName);

        //判断用户输入的是否是 汉字
        if (state == CHINESE_CHARACTER) {
            return chineseCharactersFuzzySearch(convertName, convertUserName);
        }

        //判断用户输入的是否是 拼音
        if (state == PIN_YIN) {
            return pinyinFuzzySearch(convertName, convertUserName);
        }

        //判断用户输入的 即输入拼音也输入汉字
        if (state == WHOLE) {
            return null;
        }

        return null;
    }


    /**
     * 将中文汉字替换为阿拉伯数字
     * @param input
     * @return
     */
    public static String replaceChineseNumber(String input) {
        String[] chineseNumbers = {"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "零"};
        String[] arabicNumbers = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "0"};

        for (int i = 0; i < chineseNumbers.length; i++) {
            input = input.replace(chineseNumbers[i], arabicNumbers[i]);
        }

        return input;
    }




}

🐛 注意

搜索阿拉伯数字的业务逻辑: 假设存储的用户姓名为"张三2", 用户输入的内容为3, 在后台的逻辑是需要将"张三2"用正则表达式替换为"张三二"然后在进行后续的拼音处理, 到最后的时候需要开发者手动的调用replaceChineseNumber(String input)方法将中文汉字替换为阿拉伯数字, 此时匹配上的数据是两条"张三2","张三二" 其中有一条数据是冗余的, 这块开发者需要对应自己的业务进行自行处理

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Max恒

为了开源加油 ! !

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值