java 拼音搜索功能设计与实现
前言
在搜索场景中,有下面这种需求,即搜索用户的中文拼音,简拼或全拼,甚至拼音的前几位字母时,能够快速检索出来,如下所示
我们希望得到下面这种效果
这就是一个典型的利用拼音检索功能实现对用户数据搜索的业务,这个看起来简单但实用的功能如何实现呢?
实现思路分析
1、借助es
如果您的用户数据是放在es里面的,那么在存储用户数据的时候,考虑为用户的索引中冗余一个用户中文名称的拼音字段,那么检索的时候,可以将这个拼英字段作为搜索条件进行搜索,es对拼英提供分词的能力
2、直接在mysql中做
如果您的用户数据直接存在mysql表中,同样,冗余出一个拼音字段来,查询的时候可以考虑mysql自身的模糊匹配,或者locate函数,将符合条件的数据查询出来
以上是2种基本实现此功能的思路,但从中,可以捕捉到一个关键的信息就是,需要在入库(es或mysql)的时候,生成一个账户对应的拼音字段,这个转换是关键,这里就需要借助一个外部的组件,本文采用pinyin4j
功能设计点
有了上面的基础实现思路,这还不够,还需要考虑的点包括,
- 该搜索功能支持哪些场景的搜索,如前缀拼音?中间任何一个拼音?全拼?中文名字简拼?
- 如果中文姓名是多音字,又该如何?
在调研了一部分真实用户的实际需求场景后发现下面的线索:
- 使用拼音检索希望缩小检索的范围,用户有时候会忘记目标检索对象的全名,只记得姓氏
- 更偏向于姓氏前几位,即输入姓氏的某几位,就能给出一批大致符合条件的用户列表
- 希望一些多音字的名字,也可以支持搜索
基于上面已知的业务信息,下面就用代码实现这个功能吧
功能实现步骤
前置准备
- 准备一张用户表,注意需要冗余一个拼音字段
- 搭建一个springboot工程
1、引入pinyin4j依赖
<dependency><groupId>com.belerweb</groupId><artifactId>pinyin4j</artifactId><version>2.5.0</version></dependency>紧接着我们需要考虑的是,在什么样的场景下,需要将这个用户名称的拼音字段存进去呢?很容易想到,新增一个用户,或者修改用户信息的时候,所以需要提供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<String> allStrs = Arrays.asList(splitStr);List<String> 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条解释说明,
该方法即把上面拼音转换工具类中的所有方法全部调起来使用了,工具类方法本身并不太难,但是需要结合自身的业务场景合理使用
关于多音字处理
在上午中,我们还提到,在实际的用户名称中,存在那些多音字的场景,比如: 单,正常解析出来就是 “dan” ,很明显这是不符合要求的,姓氏中应该解析为 “shan” (忽略 chan) ,或 “解” ,就应该解析为 “xie” ,这样分析之后发现,解析 “解小龙” 这个名字时,如果按照上面的工具类,解析出来的应该是 : jiexiaobin,jxb,如果再经过多音字的解析,还应该解析出 “xiexiaobin” 这个拼音,那么完整的冗余 keyWord字段值为:jiexiaobin,jxb,jie,xiexiaobin(考虑到使用系统的用户并不知道哪些是多音字)
解析多音字比较常用的做法是,维护一个常用的多音字的字典对照表,这个和 es中维护的停用词字典很像,这里直接列出提供参考,后续可以手动添加
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<String, List<String>> pinyinMap = new HashMap<>();private static Map<String, List<String>> 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<String> 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<String> 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);}}}}写一个方法测试下,效果如下:
最后,提供一个新增用户的接口吧,测试一下接口的功能是否能满足要求
@PostMapping("/save")public String save(@RequestBody DbUser dbUser){return dbUserService.save(dbUser);}测试场景1:正常的中文名称
测试场景2:多音字的中文名称
测试场景3:中文名字中插入英文等其他字符
按照拼音检索用户信息
查询的时候,就可以了利用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 拼音搜索功能设计与实现的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: Unity3D:粒子特效(Particl
- 下一篇: ug浩强工具有什么作用_ug浩强工具 v