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
- 下一篇: linux上的pcb设计软件,PCB设计