java 拼音搜索功能设计与实现

前言

在搜索场景中,有下面这种需求,即搜索用户的中文拼音,简拼或全拼,甚至拼音的前几位字母时,能够快速检索出来,如下所示

java 拼音搜索功能设计与实现_第1张图片

我们希望得到下面这种效果
java 拼音搜索功能设计与实现_第2张图片
这就是一个典型的利用拼音检索功能实现对用户数据搜索的业务,这个看起来简单但实用的功能如何实现呢?

实现思路分析

1、借助es

如果您的用户数据是放在es里面的,那么在存储用户数据的时候,考虑为用户的索引中冗余一个用户中文名称的拼音字段,那么检索的时候,可以将这个拼英字段作为搜索条件进行搜索,es对拼英提供分词的能力

2、直接在mysql中做

如果您的用户数据直接存在mysql表中,同样,冗余出一个拼音字段来,查询的时候可以考虑mysql自身的模糊匹配,或者locate函数,将符合条件的数据查询出来

以上是2种基本实现此功能的思路,但从中,可以捕捉到一个关键的信息就是,需要在入库(es或mysql)的时候,生成一个账户对应的拼音字段,这个转换是关键,这里就需要借助一个外部的组件,本文采用pinyin4j

功能设计点

有了上面的基础实现思路,这还不够,还需要考虑的点包括,

  • 该搜索功能支持哪些场景的搜索,如前缀拼音?中间任何一个拼音?全拼?中文名字简拼?
  • 如果中文姓名是多音字,又该如何?

在调研了一部分真实用户的实际需求场景后发现下面的线索:

  • 使用拼音检索希望缩小检索的范围,用户有时候会忘记目标检索对象的全名,只记得姓氏
  • 更偏向于姓氏前几位,即输入姓氏的某几位,就能给出一批大致符合条件的用户列表
  • 希望一些多音字的名字,也可以支持搜索

基于上面已知的业务信息,下面就用代码实现这个功能吧

功能实现步骤

前置准备

  • 准备一张用户表,注意需要冗余一个拼音字段
  • 搭建一个springboot工程
CREATE TABLE `db_user` (
  `user_id` varchar(32) NOT NULL COMMENT '用户ID',
  `tenant_id` varchar(32) NOT NULL COMMENT '租户ID',
  `realname` varchar(64) DEFAULT NULL COMMENT '昵称,表示用户真实姓名',
  `account` varchar(64) DEFAULT NULL COMMENT '帐号',
  `email` varchar(64) DEFAULT NULL COMMENT '邮箱',
  `mobile` varchar(32) DEFAULT NULL COMMENT '手机号',
  `passwd` varchar(256) NOT NULL COMMENT '密码',
  `skin` varchar(36) DEFAULT NULL COMMENT '皮肤',
  `key_word` varchar(36) DEFAULT NULL COMMENT '关键词',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

1、引入pinyin4j依赖

			
				com.belerweb
				pinyin4j
				2.5.0
			

紧接着我们需要考虑的是,在什么样的场景下,需要将这个用户名称的拼音字段存进去呢?很容易想到,新增一个用户,或者修改用户信息的时候,所以需要提供2个基础的接口,接口实现本身并不难,也就是入库的操作

public String save(DbUser dbUser) {
        DbUser insertUser = new DbUser();
        String userId = UUIDUtils.random();
        BeanUtils.copyProperties(dbUser,insertUser);
        insertUser.setUserId(userId);
        //设置拼音字段
        setKeyWordField(insertUser );
        dbUserMapper.insert(insertUser);
        return "success";
    }

重点考虑的是,保存到key_word 这个字段的拼音存储姓氏,即 realname ——> key_word 的映射 ,那么就需要使用到pinyin4j的提供的相关api做转换操作了,所以接下来,我们需要提供相关的工具类,对生成key_word 的数据做转换

这个key_word 里面要存储什么样的数据呢?结合上文的业务分析,这里为了后续支持的搜索的方式更丰富,考虑存储的格式如下,以 : 黄小斌 这个名字为例,最后希望转换得到的结果是: huangxiaobin,hxb,即全拼和简拼,为了提升姓氏的检索效率,在将姓氏前缀也提取出来一起拼进去,那么最后的结果是: huangxiaobin,hxb,huang ,中间以逗号分割

2、转换工具类

package com.congge.util;

import com.alibaba.dubbo.common.utils.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
import org.apache.commons.lang3.StringUtils;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 中文名字转拼音工具类
 *
 * @author zhangcy
 * @date 2021-11-04
 */
@Slf4j
public class PinYinUtils {

    private final static int[] li_SecPosValue = {1601, 1637, 1833, 2078, 2274,
            2302, 2433, 2594, 2787, 3106, 3212, 3472, 3635, 3722, 3730, 3858,
            4027, 4086, 4390, 4558, 4684, 4925, 5249, 5590};

    private final static String[] lc_FirstLetter = {"a", "b", "c", "d", "e",
            "f", "g", "h", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
            "t", "w", "x", "y", "z"};

    /**
     * 取得给定汉字串的首字母串,即声母串
     *
     * @param str 给定汉字串
     * @return 声母串
     */
    public static String getAllFirstLetter(String str) {
        if (str == null || str.trim().length() == 0) {
            return "";
        }

        String _str = "";
        for (int i = 0; i < str.length(); i++) {
            _str = _str + getFirstLetter(str.substring(i, i + 1));
        }

        return _str;
    }

    /**
     * 取得给定汉字的首字母,即声母
     *
     * @param chinese 给定的汉字
     * @return 给定汉字的声母
     */
    public static String getFirstLetter(String chinese) {
        if (chinese == null || chinese.trim().length() == 0) {
            return "";
        }
        chinese = conversionStr(chinese, "GB2312", "ISO8859-1");

        if (chinese.length() > 1) // 判断是不是汉字
        {
            int li_SectorCode = (int) chinese.charAt(0); // 汉字区码
            int li_PositionCode = (int) chinese.charAt(1); // 汉字位码
            li_SectorCode = li_SectorCode - 160;
            li_PositionCode = li_PositionCode - 160;
            int li_SecPosCode = li_SectorCode * 100 + li_PositionCode; // 汉字区位码
            if (li_SecPosCode > 1600 && li_SecPosCode < 5590) {
                for (int i = 0; i < 23; i++) {
                    if (li_SecPosCode >= li_SecPosValue[i]
                            && li_SecPosCode < li_SecPosValue[i + 1]) {
                        chinese = lc_FirstLetter[i];
                        break;
                    }
                }
            } else // 非汉字字符,如图形符号或ASCII码
            {
                chinese = conversionStr(chinese, "ISO8859-1", "GB2312");
                chinese = chinese.substring(0, 1);
            }
        }

        return chinese;
    }

    /**
     * 字符串编码转换
     *
     * @param str           要转换编码的字符串
     * @param charsetName   原来的编码
     * @param toCharsetName 转换后的编码
     * @return 经过编码转换后的字符串
     */
    public static String conversionStr(String str, String charsetName, String toCharsetName) {
        try {
            str = new String(str.getBytes(charsetName), toCharsetName);
        } catch (UnsupportedEncodingException ex) {
            System.out.println("字符串编码转换异常:" + ex.getMessage());
        }
        return str;
    }

    /**
     * 首字母大写
     *
     * @param name 参数中文字符串
     * @return result
     * @throws {@link BadHanyuPinyinOutputFormatCombination}
     */
    public static String getChinesePinyinFromName(String name) {
        String result = null;
        try {
            HanyuPinyinOutputFormat pyFormat = new HanyuPinyinOutputFormat();
            pyFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
            pyFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
            pyFormat.setVCharType(HanyuPinyinVCharType.WITH_V);
            result = PinyinHelper.toHanyuPinyinString(name, pyFormat, "");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public static boolean isChineseName(String name) {
        boolean result = true;
        if (StringUtils.isNotEmpty(name)) {
            String[] strChars = name.split("");
            for (String singleStr : strChars) {
                if (!isContainChinese(singleStr)) {
                    result = false;
                    break;
                }
            }
        }
        return result;
    }

    public static boolean isContainChinese(String str) {
        Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
        Matcher m = p.matcher(str);
        if (m.find()) {
            return true;
        }
        return false;
    }

    public static String getChineseFirstPingYingName(String str) {
        String[] split = str.split("");
        return PinYinUtils.getChinesePinyinFromName(split[0]);
    }

    public static String convert(String chineseName) {
        String nameVar1 = getChinesePinyinFromName(chineseName);
        String nameVar2 = getAllFirstLetter(chineseName);
        String nameVar3 = getChineseFirstPingYingName(chineseName);
        String result = nameVar1 + "," + nameVar2 + "," + nameVar3;
        return result;
    }

    public static boolean isMixedStr(String realname) {
        String[] splitStr = realname.split("");
        List allStrs = Arrays.asList(splitStr);
        List allLastStr = new ArrayList<>();
        boolean hasChinese = false;
        for (String single : splitStr) {
            if (isChineseName(single)) {
                hasChinese = true;
            } else {
                allLastStr.add(single);
            }
        }
        if (hasChinese) {
            if (CollectionUtils.isNotEmpty(allStrs) && CollectionUtils.isNotEmpty(allLastStr) && allLastStr.size() < allStrs.size()) {
                hasChinese = true;
            }
        }
        return hasChinese;
    }


    public static void main(String[] args) {
        PinYinUtils pinYinUtils = new PinYinUtils();
        String chineseName = "胡";
        String nameVar1 = pinYinUtils.getChinesePinyinFromName(chineseName);
        String nameVar2 = pinYinUtils.getAllFirstLetter(chineseName);
        String nameVar3 = pinYinUtils.getChineseFirstPingYingName(chineseName);
        System.out.println(nameVar1);
        System.out.println(nameVar2);
        System.out.println(nameVar3);
    }

    private static PinYinMultiCharactersUtils pinYinMultiCharactersUtils = new PinYinMultiCharactersUtils();

    /**
     * 如果是中文何字符串等混合过来的,只需原样解析,比如:111董aaa飞飞333 ,解析为:111dongaaafeifei333
     *
     * @param realname
     * @return
     */
    public String getMixPinyinStr(String realname) {
        if (StringUtils.isEmpty(realname)) {
            return null;
        }
        String[] splitStr = realname.split("");
        StringBuilder stringBuilder = new StringBuilder();
        int firstIndex = 0;
        for (String single : splitStr) {
            if (isChineseName(single)) {
                //只有第一个中文多音字做解析
                if (firstIndex == 0 && pinYinMultiCharactersUtils.isMultiChineseWord(single)) {
                    String chinesePinyinFromName = pinYinMultiCharactersUtils.getMultiCharactersPinYin(single);
                    stringBuilder.append(chinesePinyinFromName);
                    continue;
                }
                String chinesePinyinFromName = getChinesePinyinFromName(single);
                stringBuilder.append(chinesePinyinFromName);
                firstIndex++;
            } else {
                stringBuilder.append(single);
            }
        }
        return stringBuilder.toString();
    }
}

关于工具类中的一些方法,通过注释想必大家也能看懂,下面要重点说下,如何使用这个工具类呢?还是回到上面那个saveUser的方法中,如何设置这个keyWord的属性值上面来,请看下面这个方法,我们以这个方法为例做深入的剖析

private void setKeyWordField(DbUser userRequest) {
        if(StringUtils.isEmpty(userRequest.getRealname())){
            return;
        }
        /**
         * 1、pinYinUtils.isChineseName 判断传入过来的名称是否是中文呢?如果全部是中文的话做基础的解析
         * 2、pinYinUtils.convert 做拼音转换
         * 3、pinYinMultiCharactersUtils.getMultiCharactersPinYin 如果名字中的姓氏是多音字时,还需要做一下特别处理
         * 4、如果用户名中不全是中文,比如: 周小斌_bank_1 ,类似这样的,或者 : bank_1_周小斌 ,只转换其中的中文,不改变整个字符串的顺序
         */

        if (pinYinUtils.isChineseName(userRequest.getRealname())) {
            String originalConvert = pinYinUtils.convert(userRequest.getRealname());
            String multiConvertResult =  pinYinMultiCharactersUtils.getMultiCharactersPinYin(userRequest.getRealname());
            if(StringUtils.isNotEmpty(multiConvertResult)){
                userRequest.setKeyWord(originalConvert.concat(",").concat(multiConvertResult));
                return;
            }
            userRequest.setKeyWord(originalConvert );
        }else {
            //如果不全部是中文,即除了中文之外,还有其他字符混在一起的话,这种才做解析
            if(pinYinUtils.isMixedStr(userRequest.getRealname())){
                userRequest.setKeyWord(pinYinUtils.getMixPinyinStr(userRequest.getRealname()));
            }
        }
    }

参考其中的4条解释说明,

  1. pinYinUtils.isChineseName 判断传入过来的名称是否是中文呢?如果全部是中文的话做基础的解析
  2. pinYinUtils.convert 做拼音转换
  3. pinYinMultiCharactersUtils.getMultiCharactersPinYin 如果名字中的姓氏是多音字时,还需要做一下特别处理
  4. 如果用户名中不全是中文,比如: 周小斌_bank_1 ,类似这样的,或者 : bank_1_周小斌 ,只转换其中的中文,不改变整个字符串的顺序

该方法即把上面拼音转换工具类中的所有方法全部调起来使用了,工具类方法本身并不太难,但是需要结合自身的业务场景合理使用

关于多音字处理

在上午中,我们还提到,在实际的用户名称中,存在那些多音字的场景,比如: 单,正常解析出来就是 “dan” ,很明显这是不符合要求的,姓氏中应该解析为 “shan” (忽略 chan) ,或 “解” ,就应该解析为 “xie” ,这样分析之后发现,解析 “解小龙” 这个名字时,如果按照上面的工具类,解析出来的应该是 : jiexiaobin,jxb,如果再经过多音字的解析,还应该解析出 “xiexiaobin” 这个拼音,那么完整的冗余 keyWord字段值为:jiexiaobin,jxb,jie,xiexiaobin(考虑到使用系统的用户并不知道哪些是多音字)

解析多音字比较常用的做法是,维护一个常用的多音字的字典对照表,这个和 es中维护的停用词字典很像,这里直接列出提供参考,后续可以手动添加

java 拼音搜索功能设计与实现_第3张图片

a#阿
ao#拗口/违拗/拗断/执拗/拗口/拗口风/拗口令/拗曲/拗性/拗折/警拗
ai#艾
bang#膀/磅/蚌
ba#扒
bai#叔伯/百/柏杨/㧳/梵呗/呗佛/呗音/呗唱/呗偈/呗声/呗赞/赞呗
bao#剥皮/薄/暴/堡/曝
bei#呗
beng#蚌埠
bi#复辟/臂/秘鲁/泌阳
bing#屏息/屏弃/屏气/屏除/屏声
bian#扁/便/便宜坊
bo#薄荷/单薄/伯/泊/波/柏/萝卜/孛
bu#卜/柨
can#参
cang#藏/欌
cen#参差
ceng#曾/噌
cha#差/刹那/宝刹/一刹/查/碴/喳喳/喀喳
chai#公差/差役/专差/官差/听差/美差/办差/差事/差使/肥差/当差/钦差/苦差/出差
chan#颤/单于/禅
chang#长/厂
chao#朝/嘲/焯
che#工尺/车
chen#称职/匀称/称心/相称/对称
cheng#称/乘/澄/噌吰/橙 秤/盛满/盛器/盛饭
chu#畜
chui#椎心
chuai#揣
chuan#传
chi#匙/尺/吃
chong#重庆/重重/虫
chou#臭/帱
chuang#经幢
chuo#绰
ci#参差/鳞差/伺候/龟兹
cuan#攒聚/攒动/攒集/攒宫/攒所
cuo#撮儿/撮要/撮合
da#大/嗒
dao#叨/帱载/帱察
dai#大夫
dan#单/弹/掸/澹
dang#铛
de#的/得
di#堤/底/怎的/有的/目的/标的/打的/的确/有的放/的卢/矢之的/言中的/语中的/的士/地/提防/快的/美的
diao#蓝调/调调/音调/论调/格调/调令/低调/笔调/基调/强调/声调/滥调/老调/色调/单调/腔调/跑调/曲调/步调/语调/主调/情调
ding#丁
du#读/都/度
dou#全都/句读
duo#舵/测度/忖度/揣度/猜度
dun#粮囤/盾/顿/沌/敦
e#阿谀/阿胶/阿弥/恶/擜
er#儿
fan#番
feng#冯
fei#婔
fo#佛
fu#仿佛/果脯/罘/莩
fou#否
fiao#覅
ga#咖喱/伽马/嘎/戛纳
gai#盖
gao#告
gang#扛鼎
ge#革/蛤蚧/文蛤/蛤蜊/咯
gei#给
geng#脖颈
gong#女红/共
gu#谷/中鹄/鼓
gui#龟/柜/硅/倭傀/傀异/傀然/傀垒/傀怪/傀卓/傀奇/傀伟/傀民/傀俄/琦傀/奇傀
gua#呱
guan#纶巾/东莞
guang#广
ha#蛤/哈/虾蟆
hai#还/嗨/咳声/咳笑
hao#貉子/貉绒
hang#夯/总行/分行/支行/行业/排行/行情/央行/商行/外行/银行/中行/交行/招行/农行/工行/建行/商行/酒行/麻行/琴行/行业/同行/行列/行货/行会/行家/巷道/引吭/扼吭/批吭/搤吭/高吭/喉吭/咔吭/絶吭/吭嗌/吭咽/吭首
he#和/合/核/鶴/猲
heng#道行/涥
hu#鹄/水浒/嗀/唬
hua#滑/呚/椛
huan#归还/放还/奉还/圜
hui#会/浍河/媈/灳/哕/瑗珲
hong#红/虹
huo#软和/热和/暖和
hun#尡/珲
ji#病革/给养/自给/给水/薪给/给予/供给/稽/缉/藉/奇数/亟/诘屈/荠菜/愱
jia#雪茄/伽/家/价/贾/戛
jian#见/浅浅
jiang#降
jiao#嚼舌/嚼字/嚼蜡/角/剿/饺/脚/蕉/矫/睡觉/侥/校对/校验/校正/校准/审校/校场/校核/校勘/校订/校阅/校样
jie#慰藉/蕴藉/诘/媘/煯
jin#矜/劲/禁
jing#颈/景/强劲/劲风/劲旅/劲敌/劲射/苍劲/遒劲/劲草
jiong#炅
ju#咀/居/桔/句/婮
jun#均
juan#棚圈/圈养/猪圈/羊圈
jue#主角/角色/旦角/女角/丑角/角力/名角/配角/嚼/觉/䏐
jun#龟裂/俊
ka#咖/卡/喀
kai#楷
kang#扛
ke#咳/壳
keng#吭
kuai#会计/财会/浍
kui#傀
kuo#括
la#癞痢/腊/蜡
lai#癞疮/癞子/癞蛤/癞皮
lao#积潦/络子/落枕/落价/粩/姥
le#乐/勒/了
lei#勒紧
lo#然咯
lou#佝偻/泄露/露面/露脸/露骨/露底/露馅/露一手/露相/露马脚/露怯
long#里弄/弄堂/泷
li#跞/礼/櫔/栃
liao#了解/了结/明了/了得/末了/未了/了如/潦/撩
liang#靓/俩
lie#挘
lin#崊
ling#霗/令
liu#六/遛
lu#碌/陆/露
luo#络/落/漯/囖/洜/泺
lv#率/绿
lve#鋢/稤
lun#纶
ma#嫲/抹布/抹脸/抹桌子/摩挲
mai#埋
man#埋怨/蔓
mai#脉
mang#氓/芒
mao#冒
me#嚒
men#椚
meng#群氓/盟/癦
mei#没/旀
mo#淹没/没收/出没/沉没/没落/吞没/覆没/没入/埋没/鬼没/隐没/湮没/辱没/脉脉/模/摩/抹
mou#绸缪/牟
mi#秘/泌尿/分泌/谜/檷枸
mian#渑
ming#掵
miu#谬/谬论/纰缪
mu#大模/字模/模板/模样/模具/装模/模子/牟尼/子牟/夷牟/悬牟/相牟/头牟/宾牟/曹牟/岑牟/兜牟/卢牟/弥牟/牟食/牟槊/牟衫/牟光/牟牟/牟甲
na#哪/娜/那
nao#臑
nan#南
ne#哪吒/呢
nei#氞
neus#莻
nong#弄/燶
ni#毛呢/花呢/呢绒/线呢/呢料/呢子/呢喃/溺/檷
niao#尿/鸟/便溺
nian#粘膜/粘度/粘土/粘合剂/粘液/粘稠/粘合/粘着/粘结/粘性/粘附/不粘锅/粘糊/粘虫/粘聚/粘滞/焾/哖
niang#酿
nin#脌
ning#倿/拧
niu#拗/汼
nu#努
nuo#婀娜/袅娜/喏
nv#女
nve#疟/硸
o#喔/筽
ou#膒
pa#扒手/扒窃/扒外/扒分/扒糕/扒灰/扒犁/扒龙/扒搂/扒山虎/扒艇
pai#派/迫击/迫击炮
pao#刨/炮/萢
pan#番禺
pang#胖/膀/磅
pei#蓜
pi#辟/否极/臧否/龙陂/芘
pian#扁舟/便宜/魸
piao#朴姓/饿莩/饥莩/葭莩
pin#穦
ping#屏/苹/冯河
po#湖泊/血泊 /迫/朴刀/坡/陂
pu#一曝十寒/里堡/十里堡/脯/朴/曝晒/瀑/埔
qi#期/其/泣/祇
qiu#龟兹/湭
qi#稽首/缉鞋/栖/奇/漆/齐
qia#卡脖/卡子/关卡/卡壳/哨卡/边卡/发卡/峠
qiao#雀盲/雀子/地壳/甲壳/躯壳
qian#纤/乾/浅
qiang#强/㛨/㩖/䅚/䵁
qie#茄/趔趄/聺/籡
qin#亲/沁
qing#干亲/亲家
qiong#熍
qu#区/趣/爠
quan#圈/券
que#雀/炔
re#声喏/唱喏
rong#嬫
ruo#若/嵶
saeng#栍
sang#槡
sai#塞/嘥
sao#螦
se#堵塞/搪塞/茅塞/闭塞/鼻塞/梗塞/阻塞/淤塞/拥塞/哽塞/色
sha#莎/刹车/急刹/厦/杉木/杉篙
shai#色子
shao#勺/红苕
shan#姓单/单/单县/杉/敾/禅让/受禅/禅变/禅代/禅诰
shang#衣裳
she#拾级/折本/射/蛇
shen#沙参/野参/参王/人参/红参/丹参/山参/海参/鹿参/什么/身/沈/桑椹/食椹/烂椹/木椹
sheng#野乘/千乘/史乘/省/晟/盛/陹/渑水
shi#钥匙/什/识/似的/食/石/氏/拾/适/瑡
shiwa#瓧
shuai#表率/率性/率直/率真/粗率/率领/轻率/直率/草率/大率/坦率/衰
shuang#泷水/鏯
shu#属/数/术/熟
shui#游说
shuo#数见/说
si#伺/似/思
sou#蓃/摗
su#宿/鯂
sui#尿泡
ta#拓片/拓印/拓本/拓墨/拓写/拓手/拓工/碑拓/疲沓/拖沓/杂沓/沓/塔/鸿塔
tang#汤/镗
tao#陶
tan#反弹/弹性/弹簧/弹力/弹奏/弹跳/弹指/弹劾/弹唱/弹射/弹性体/吹弹/评弹/乱弹琴/弹压/弹指/弹簧/弹冠/弹雀/弹雀/弹丝/弹丸/澹台
te#脦
teng#虅
ti#提/体
tiao#调/苕
ting#町/听
tong#通
tu#迌
tuan#湪
tui#褪
tuo#拓/袥
tun#囤/屯
wei#尾/蔚/圩堤/圩垸/圩田/圩子/赶圩/歌圩
weng#攚
wu#无/可恶/交恶/好恶/厌恶/憎恶/嫌恶/痛恶/深恶/兀
wan#藤蔓/枝蔓/根蔓/蔓草/瓜蔓/蔓儿/莞/万/百万/皖
wang#亡
wai#崴
xia#虾/吓/夏/厦门/厦大/唬杀
xi#栖/系/蹊/洗/溪/戏/焁/铣/褶衣/褶裤
xiao#校/切削/削面/刀削/刮削
xian#纤细/光纤/纤巧/纤柔/纤小/纤维/纤瘦/纤纤/化纤/纤秀/棉纤/纤尘/铣铁/金铣
xiang#投降/巷
xie#解/解数/出血/采血/换血/血糊/尿血/淤血/放血/血晕/血淋/便血/吐血/咯血/叶韵/蝎/蝎子/邪/猲猲
xin#嬜/邤
xiu#铜臭/乳臭/成宿/星宿/璓
xin#馨/信/鸿信
xing#深省/省视/内省/不省人事/省悟/省察/行/荥
xiong#匂
xu#牧畜/畜产/畜牧/畜养/并畜/畜锐/吁/圩/浒
xuan#箮
xue#削/血/樰
xun#荨/寻
ya#琊
yao#钥/耀/曜/佋侥/侥觎/侥僺/侥利/侥傒/侥觊/侥会/侥滥/侥望/侥求/侥竞/侥薄/侥躐/侥取/侥奇/侥忝/侥速/侥冀/侥冒/疟子
yan#咽/殷红/朱殷/腌/烟/曕
ye#液/抽咽/哽咽/咽炎/呜咽/幽咽/悲咽/叶/葉/璍/潱/拽步/拽扶/拽扎
yi#自艾/遗/屹/嬄/噫
yin#殷/栶
ying#荥经/緓/灜
yo#杭育
yong#涌/硧
you#牗
yu#余/呼吁/吁请/吁求/育/熨帖/熨烫/於
yuan#员/茒/圜丘
yun#熨
yue#约/乐音/器乐/乐律/乐章/音乐/乐理/民乐/乐队/声乐/奏乐/弦乐/乐坛/管乐/配乐/乐曲/乐谱/锁钥/密钥/乐团/乐器/嬳/咽哕/唾哕/发哕/干哕/哕吐/哕饭/哕呕/哕息/哕厥/哕噫/哕逆/哕咽/哕骂/哕心/哕喈/口哕/呕哕
za#绑扎/结扎/包扎/捆扎/咱家
zan#攒/咱
zang#宝藏/藏历/藏文/藏语/藏青/藏族/藏医/藏药/藏蓝/西藏
zai#牛仔/龟仔/龙仔/鼻仔/羊仔/仔仔/麻仔/麵包仔/麦旺仔/鸿仔/煲仔/福仔/畠
zao#栆
ze#择
zeng#曾国藩/曾孙/曾祖父/曾祖/曾祖母/曾孙女/曾巩/囎/缯
zong#综/繌
zha#扎/柞狭/柞薪/柞子/柞鄂/柞叶/柞撒/槱柞/一柞/五柞宫/五柞/雠柞/芟柞/蜡祭/喳
zhai#宅/夈/择席/择菜
zhan#粘
zhang#列车长/行长/村长/镇长/乡长/区长/县长/市长/省长/会长/班长/排长/连长/营长/团长/旅长/师长/军长/委员长/局长/厅长/所长/部长/组长/生长/长大/长高/长个/
zhao#朝朝/明朝/朝晖/朝夕/朝思/今朝/朝气/朝三/朝秦/朝霞/鹰爪/龙爪/魔爪/爪牙/着急/着迷/着火/怎么着/正着/着凉/一着/犯不着/着数/这么着/犯得着/着慌/着忙/数得着/龙爪槐/嘲哳/嘲惹
zhe#折/着/褶
zhen#殝/椹
zhi#标识/吱/殖/枝/方祇/后祇/皇祇/黄祇/皇地祇/金祇/祇树/月氏
zhong#重/种
zhou#粥
zhu#属意/著/駯
zhua#爪子
zhuai#拽
zhuan#芈月传/外传/传记/自传/正传/小传/评传/传略/别传
zhui#椎/隹
zhuo#执著/着装/着落/着意/着力/附着/着笔/胶着/着实/衣着/着眼/着想/着重/穿着/执着/着墨/着实/沉着/着陆/着想/着色/焯见/焯烁/辉焯
zhuang#幢房/一幢/幢楼/庒
zi#仔/兹
zu#足
zuo#柞/穝

最后再提供一个解析多音字的工具类

package com.congge.utils.pyin;

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 处理多音字的扩展工具类
 *
 * @author zhangcy
 * @date 2021-12-02
 */
public class PinYinMultiCharactersUtils {

    private static final Logger logger = LoggerFactory.getLogger(PinYinMultiCharactersUtils.class);

    private static Map> pinyinMap = new HashMap<>();

    private static Map> otherSpecialWord = new HashMap<>();

    static {
        //这里如果apollo上面没有配置任何的值,默认初始化一些常用的
        otherSpecialWord.put("解", Arrays.asList("xie"));
        otherSpecialWord.put("查", Arrays.asList("zha"));
        otherSpecialWord.put("单", Arrays.asList("shan"));
        otherSpecialWord.put("朴", Arrays.asList("piao"));
        otherSpecialWord.put("区", Arrays.asList("ou"));
        otherSpecialWord.put("仇", Arrays.asList("qiu"));
        otherSpecialWord.put("阚", Arrays.asList("kan"));
        otherSpecialWord.put("种", Arrays.asList("chong"));
        otherSpecialWord.put("盖", Arrays.asList("ge"));
        otherSpecialWord.put("繁", Arrays.asList("po"));

    }

    public static String toPinyin(String str) {
        try {
            initPinyin("/duoyinzi.dic.txt");
            String py = convertChineseToPinyin(str);
            System.out.println(str + " = " + py);
            return py;
        } catch (Exception e) {
            logger.error("convert pinyin error,e : {}", e);
            return null;
        }
    }

    /**
     * 通过拆分名字的方式 获取多音字的名字的完整拼音
     *
     * @param chinese
     * @return
     */
    public static String getMultiCharactersPinYin(String chinese) {
        if (StringUtils.isEmpty(chinese)) {
            return null;
        }
        String result = null;
        if (chinese.length() >= 2) {
            String[] nameElements = chinese.split("");
            String firstName = nameElements[0];
            if (!isMultiChineseWord(firstName)) {
                return null;
            }
            String secondName = null;
            StringBuilder sb = new StringBuilder();
            for (String str : nameElements) {
                if (!str.equals(firstName)) {
                    sb.append(str);
                }
            }
            secondName = sb.toString();
            //获取多音字的拼音
            String partOne = PinYinMultiCharactersUtils.toPinyin(firstName);
            String partTwo = PinYinMultiCharactersUtils.toPinyin(secondName);
            result = partOne.concat(partTwo).toLowerCase();
        } else {
            result = PinYinMultiCharactersUtils.toPinyin(chinese);
        }
        return result;
    }

    /**
     * 将某个字符串的首字母大写
     *
     * @param str
     * @return
     */
    public static String convertInitialToUpperCase(String str) {
        if (str == null) {
            return null;
        }
        StringBuffer sb = new StringBuffer();
        char[] arr = str.toCharArray();
        for (int i = 0; i < arr.length; i++) {
            char ch = arr[i];
            if (i == 0) {
                sb.append(String.valueOf(ch).toUpperCase());
            } else {
                sb.append(ch);
            }
        }

        return sb.toString();
    }

    /**
     * 判断当前中文字是否多音字
     *
     * @param chinese
     * @return
     */
    public static boolean isMultiChineseWord(String chinese) {
        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        char[] arr = chinese.toCharArray();
        for (int i = 0; i < arr.length; i++) {
            char ch = arr[i];
            if (ch > 128) {
                // 非ASCII码,取得当前汉字的所有全拼
                try {
                    String[] results = PinyinHelper.toHanyuPinyinStringArray(ch, defaultFormat);
                    if (results == null) {
                        //非中文
                        return false;
                    } else {
                        int len = results.length;
                        if (len == 1) {
                            // 不是多音字
                            return false;
                        } else if (results[0].equals(results[1])) {
                            //非多音字 有多个音,默认取第一个
                            if (otherSpecialWord.containsKey(chinese)) {
                                return true;
                            }
                            return false;
                        } else {
                            // 多音字
                            return true;
                        }
                    }
                } catch (BadHanyuPinyinOutputFormatCombination e) {
                    logger.error("BadHanyuPinyinOutputFormatCombination ,e :{}", e);
                }
            }
        }
        return false;
    }

    /**
     * 汉字转拼音 最大匹配优先
     *
     * @param chinese
     * @return
     */
    private static String convertChineseToPinyin(String chinese) {
        StringBuffer pinyin = new StringBuffer();
        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        char[] arr = chinese.toCharArray();
        for (int i = 0; i < arr.length; i++) {
            char ch = arr[i];
            if (ch > 128) {
                // 非ASCII码 取得当前汉字的所有全拼
                try {
                    String[] results = PinyinHelper.toHanyuPinyinStringArray(ch, defaultFormat);
                    if (results == null) {  //非中文
                        return "";
                    } else {
                        int len = results.length;
                        if (len == 1) {
                            // 不是多音字
                            String py = results[0];
                            if (py.contains("u:")) {  //过滤 u:
                                py = py.replace("u:", "v");
                                logger.info("filter u: {}", py);
                            }
                            pinyin.append(convertInitialToUpperCase(py));
                        } else if (results[0].equals(results[1])) {
                            //非多音字 有多个音,取第一个
                            if (otherSpecialWord.containsKey(chinese)) {
                                return otherSpecialWord.get(chinese).get(0);
                            }
                            pinyin.append(convertInitialToUpperCase(results[0]));
                        } else {
                            logger.info("多音字:{}", ch);
                            if (otherSpecialWord.containsKey(chinese)) {
                                pinyin.append(otherSpecialWord.get(chinese).get(0));
                                continue;
                            }
                            int length = chinese.length();
                            boolean flag = false;
                            String s = null;
                            List keyList = null;
                            for (int x = 0; x < len; x++) {
                                String py = results[x];
                                if (py.contains("u:")) {
                                    py = py.replace("u:", "v");
                                    logger.info("filter u :{}", py);
                                }
                                keyList = pinyinMap.get(py);
                                if (i + 3 <= length) {
                                    //后向匹配2个汉字  大西洋
                                    s = chinese.substring(i, i + 3);
                                    if (keyList != null && (keyList.contains(s))) {
                                        pinyin.append(convertInitialToUpperCase(py));
                                        flag = true;
                                        break;
                                    }
                                }
                                if (i + 2 <= length) {
                                    //后向匹配 1个汉字  大西
                                    s = chinese.substring(i, i + 2);
                                    if (keyList != null && (keyList.contains(s))) {
                                        pinyin.append(convertInitialToUpperCase(py));
                                        flag = true;
                                        break;
                                    }
                                }
                                if ((i - 2 >= 0) && (i + 1 <= length)) {
                                    // 前向匹配2个汉字 龙固大
                                    s = chinese.substring(i - 2, i + 1);
                                    if (keyList != null && (keyList.contains(s))) {
                                        pinyin.append(convertInitialToUpperCase(py));
                                        flag = true;
                                        break;
                                    }
                                }
                                if ((i - 1 >= 0) && (i + 1 <= length)) {
                                    // 前向匹配1个汉字   固大
                                    s = chinese.substring(i - 1, i + 1);
                                    if (keyList != null && (keyList.contains(s))) {
                                        pinyin.append(convertInitialToUpperCase(py));
                                        flag = true;
                                        break;
                                    }
                                }
                                if ((i - 1 >= 0) && (i + 2 <= length)) {
                                    //前向1个,后向1个  固大西
                                    s = chinese.substring(i - 1, i + 2);
                                    if (keyList != null && (keyList.contains(s))) {
                                        pinyin.append(convertInitialToUpperCase(py));
                                        flag = true;
                                        break;
                                    }
                                }
                            }
                            if (!flag) {
                                //都没有找到,匹配默认的 读音  大
                                s = String.valueOf(ch);
                                for (int x = 0; x < len; x++) {
                                    String py = results[x];
                                    if (py.contains("u:")) {  //过滤 u:
                                        py = py.replace("u:", "v");
                                    }
                                    keyList = pinyinMap.get(py);
                                    if (keyList != null && (keyList.contains(s))) {
                                        pinyin.append(convertInitialToUpperCase(py));//拼音首字母 大写
                                        break;
                                    }
                                }
                            }
                        }
                    }
                } catch (BadHanyuPinyinOutputFormatCombination e) {
                    logger.error("BadHanyuPinyinOutputFormatCombination :{}", e);
                }
            } else {
                pinyin.append(arr[i]);
            }
        }
        return pinyin.toString();
    }

    /**
     * 初始化 所有的多音字词组
     *
     * @param fileName
     */
    public static void initPinyin(String fileName) {
        if (pinyinMap != null && !pinyinMap.isEmpty()) {
            return;
        }
        // 读取多音字的全部拼音表;
        InputStream file = PinyinHelper.class.getResourceAsStream(fileName);
        BufferedReader br = new BufferedReader(new InputStreamReader(file));
        String s = null;
        try {
            while ((s = br.readLine()) != null) {
                if (s != null) {
                    String[] arr = s.split("#");
                    String pinyin = arr[0];
                    String chinese = arr[1];
                    if (chinese != null) {
                        String[] strs = chinese.split(" ");
                        List list = Arrays.asList(strs);
                        pinyinMap.put(pinyin, list);
                    }
                }
            }
        } catch (IOException e) {
            logger.error("IOException,{}", e);
        } finally {
            try {
                br.close();
            } catch (IOException e) {
                logger.error("IOException,{}", e);
            }
        }
    }

}


写一个方法测试下,效果如下:
java 拼音搜索功能设计与实现_第4张图片

最后,提供一个新增用户的接口吧,测试一下接口的功能是否能满足要求

    @PostMapping("/save")
    public String save(@RequestBody DbUser dbUser){
        return dbUserService.save(dbUser);
    }

测试场景1:正常的中文名称

java 拼音搜索功能设计与实现_第5张图片

java 拼音搜索功能设计与实现_第6张图片
测试场景2:多音字的中文名称
在这里插入图片描述
测试场景3:中文名字中插入英文等其他字符
java 拼音搜索功能设计与实现_第7张图片

在这里插入图片描述

按照拼音检索用户信息

查询的时候,就可以了利用keyWord这个字段进行搜索了,代码就不再写了,关键的sql语句可以参考如下:

select * from db_user where key_word like '%xie%';

select * from db_user where key_word like 'xie%';

select * from db_user where LOCATE('xie',`key_word`);

java 拼音搜索功能设计与实现_第8张图片

本篇到此结束,最后感谢观看!

你可能感兴趣的:(技术总结,java实现拼音搜索,java实现拼音检索,拼音搜索,拼音检索)