上一篇文章 Java 汉字转拼音 介绍了Java 中利用Pinyin4j 实现汉字转拼音,但是对于多音字问题采取的是组合拼音方式,例如长沙 取拼音结果就是 changsha zhangsha。某些情况下我们希望能得到多音字的唯一拼音,此时就需要借助多音字字典了,原理很简单:给多音字一个默认的拼音并告诉计算机碰到哪些词的时候使用其它的拼音,例如 长 字,我们可以给它指定默认拼音为 zhang,并标识 长沙 拼音为 chang。


工程结构

JAVA中文转ASCII java中文转拼音多音字_汉字转拼音

多音字词典

本类库 支持自定义扩展词典,词典文件名称为py4j.dic,完整路径为:resources/py4j/dictionary/py4j.dic,词典文件格式如下:

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#柞/穝




关键代码

Py4j.java

package com.bytebeats.py4j;

import com.bytebeats.py4j.exception.BadHanYuPinYinException;
import com.bytebeats.py4j.util.StringUtils;
import com.google.common.collect.ArrayListMultimap;
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;

public class Py4j {
	private ArrayListMultimap<String,String> duoYinZiMap;
	public Py4j(){
		Py4jDictionary.getDefault().init();
		duoYinZiMap = Py4jDictionary.getDefault().getDuoYinZiMap();
	}

	public String[] getPinyin(char ch) {
		try{
			HanyuPinyinOutputFormat outputFormat = new HanyuPinyinOutputFormat();
			outputFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
			outputFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
			outputFormat.setVCharType(HanyuPinyinVCharType.WITH_V);

			if(ch>=32 && ch<=125){	//ASCII >=33 ASCII<=125的直接返回 ,ASCII码表:http://www.asciitable.com/
				return new String[]{String.valueOf(ch)};
			}
			return PinyinHelper.toHanyuPinyinStringArray(ch, outputFormat);
		} catch (BadHanyuPinyinOutputFormatCombination e) {
			throw new BadHanYuPinYinException(e);
		}

	}

	public String getPinyin(String chinese) {
		if(StringUtils.isEmpty(chinese)){
			return null;
		}
		
		chinese = chinese.replaceAll("[\\.,\\,!·\\!?\\?;\\;\\(\\)()\\[\\]\\:: ]+", " ").trim();

		StringBuilder py_sb = new StringBuilder(32);
		char[] chs = chinese.toCharArray();
		for(int i=0;i<chs.length;i++){
			String[] py_arr = getPinyin(chs[i]);
			if(py_arr==null || py_arr.length<1){
				throw new BadHanYuPinYinException("pinyin array is empty, char:"+chs[i]+",chinese:"+chinese);
			}
			if(py_arr.length==1){
				py_sb.append(convertInitialToUpperCase(py_arr[0]));
			}else if(py_arr.length==2 && py_arr[0].equals(py_arr[1])){
				py_sb.append(convertInitialToUpperCase(py_arr[0]));
			}else{
				String resultPy = null, defaultPy = null;;
				for (String py : py_arr) {
					String left = null;	//向左多取一个字,例如 银[行]
					if(i>=1 && i+1<=chinese.length()){
						left = chinese.substring(i-1,i+1);
						if(duoYinZiMap.containsKey(py) && duoYinZiMap.get(py).contains(left)){
							resultPy = py;
							break;
						}
					}
					
					String right = null;	//向右多取一个字,例如 [长]沙
					if(i<=chinese.length()-2){
						right = chinese.substring(i,i+2);
						if(duoYinZiMap.containsKey(py) && duoYinZiMap.get(py).contains(right)){
							resultPy = py;
							break;
						}
					}
					
					String middle = null;	//左右各多取一个字,例如 龙[爪]槐
					if(i>=1 && i+2<=chinese.length()){
						middle = chinese.substring(i-1,i+2);
						if(duoYinZiMap.containsKey(py) && duoYinZiMap.get(py).contains(middle)){
							resultPy = py;
							break;
						}
					}
					String left3 = null;	//向左多取2个字,如 芈月[传],列车长
					if(i>=2 && i+1<=chinese.length()){
						left3 = chinese.substring(i-2,i+1);
						if(duoYinZiMap.containsKey(py) && duoYinZiMap.get(py).contains(left3)){
							resultPy = py;
							break;
						}
					}
					
					String right3 = null;	//向右多取2个字,如 [长]孙无忌
					if(i<=chinese.length()-3){
						right3 = chinese.substring(i,i+3);
						if(duoYinZiMap.containsKey(py) && duoYinZiMap.get(py).contains(right3)){
							resultPy = py;
							break;
						}
					}
					
					if(duoYinZiMap.containsKey(py) && duoYinZiMap.get(py).contains(String.valueOf(chs[i]))){	//默认拼音
						defaultPy = py;
					}
				}
				
				if(StringUtils.isEmpty(resultPy)){
					if(StringUtils.isNotEmpty(defaultPy)){
						resultPy = defaultPy;
					}else{
						resultPy = py_arr[0];
					}
				}
				py_sb.append(convertInitialToUpperCase(resultPy));
			}
		}
		
		return py_sb.toString();
	}
	
	private String convertInitialToUpperCase(String str) {
		if (str == null || str.length()==0) {
			return "";
		}
		return str.substring(0, 1).toUpperCase()+str.substring(1);
	}
}


Py4jDictionary.java

package com.bytebeats.py4j;

import com.bytebeats.py4j.util.IoUtils;
import com.bytebeats.py4j.util.StringUtils;
import com.google.common.collect.ArrayListMultimap;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;

/**
 * ${DESCRIPTION}
 *
 * @author Ricky Fung
 * @date 2017-02-16 20:16
 */
public class Py4jDictionary {

    private ArrayListMultimap<String,String> duoYinZiMap;

    private static final String PREFIX = "py4j/dictionary/";

    private static final String CONFIG_NAME = "py4j.dic";

    private static final String PINYIN_SEPARATOR = "#";

    private static final String WORD_SEPARATOR = "/";

    private volatile boolean inited;

    private Py4jDictionary(){

    }

    public void init(){
        if(inited){
            return;
        }
        System.out.println("******start load py4j config******");
        Enumeration<URL> configs = null;
        try{
            String fullName = PREFIX + CONFIG_NAME;
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            configs = cl.getResources(fullName);
        } catch (Exception e){
            e.printStackTrace();
        }

        this.duoYinZiMap = parse(configs);
        inited = true;

        System.out.println("******load py4j config over******");
        System.out.println("py4j map key size:"+duoYinZiMap.keySet().size());
    }

    private ArrayListMultimap<String,String> parse(Enumeration<URL> configs){
        ArrayListMultimap<String,String> duoYinZiMap = ArrayListMultimap.create(512, 16);
        if(configs!=null){
            while (configs.hasMoreElements()) {
                parseURL(configs.nextElement(), duoYinZiMap);
            }
        }
        return duoYinZiMap;
    }

    private void parseURL(URL url, ArrayListMultimap<String, String> duoYinZiMap){
        System.out.println("parse py4j dictionary:"+url.getPath());
        InputStream in = null;
        BufferedReader br = null;
        try {
            in = url.openStream();
            br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
            String line = null;
            while ((line = br.readLine()) != null) {

                String[] arr = line.split(PINYIN_SEPARATOR);

                if (StringUtils.isNotEmpty(arr[1])) {
                    String[] dyzs = arr[1].split(WORD_SEPARATOR);
                    for (String dyz : dyzs) {
                        if (StringUtils.isNotEmpty(dyz)) {
                            duoYinZiMap.put(arr[0], dyz.trim());
                        }
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(String.format("load py4j config:%s error", url), e);
        } finally {
            IoUtils.closeQuietly(br);
            IoUtils.closeQuietly(in);
        }
    }

    ArrayListMultimap<String,String> getDuoYinZiMap(){
        return duoYinZiMap;
    }

    public static Py4jDictionary getDefault(){
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        private static final Py4jDictionary INSTANCE = new Py4jDictionary();
    }
}



测试用例

package com.bytebeats.py4j;

import org.junit.*;

import java.util.Arrays;

/**
 * Unit test for simple App.
 */
public class Py4jTest {
	private Py4j py4j;

	@Before
	public void init(){
		py4j = new Py4j();
	}

	@Test
	public void testChinesePy() {

		final String[] arr = {"肯德基", "重庆银行", "长沙银行", "便宜坊", "西藏", "藏宝图", "出差", "参加", "列车长"};
		
		for (String chinese : arr){
			String py = py4j.getPinyin(chinese);
			System.out.println(chinese+"\t"+py);
		}
	}
	
	@Test
	public void testCharPy(){

		char[] chs = {'长', '行', '藏', '度', '阿', '佛', '2', 'A', 'a'};
		for(char ch : chs){
			String[] arr_py = py4j.getPinyin(ch);
			System.out.println(ch+"\t"+Arrays.toString(arr_py));
		}
	}

	@After
	public void destroy(){
		py4j = null;
	}
}


源代码下载

py4j:https://github.com/TiFG/py4j