🍎 介绍
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'
🔥 代码调试
- 代码中含有一个main方法, 在方法传入参数即可使用
- fuzzyQuery()就是模糊查询的方法, name即为用户输入的拼音, userName即为需要匹配的名称集合
👀 业务思路
- 当库中已进行租户隔离并且业务数据较少时,您可以使用工具类对库中的姓名进行模糊匹配(前提需要将库中所有的数据查询出来进行比对),并将结果全部查出, 再将结果去库中查询
- 当库中的数据量庞大且增长速度快时,我们建议将生成的拼音字符串存储到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","张三二" 其中有一条数据是冗余的, 这块开发者需要对应自己的业务进行自行处理