如何为 java 设计一款高性能的拼音转换工具 pinyin4j
拼音转换工具
拼音转换工具的思路不难:
(1)词语分词
(2)基于词库进行拼音的映射
(3)拼接最后的结果
可以认为主要下面的部分值得留意
准确性
作为拼音转换算法,准确性优先级应该是在性能之前的。
如果我们能保证高准确性,应该尽可能的去提高准确性。
词库来源
这里的词库,不包括分词的词库,仅仅指拼音的词库。
指拼音的词库,收集可以在各种优秀词库的基础上,不应该在收集上耗费太多时间。
分词算法
分词有许多优秀的算法,其中的学问也比较多。
分词是很多 NLP 的基础,有以下框架可供选择:
性能
性能是很多开发者的追求,天下武功,唯快不破。
所以如何提升性能呢?
主要就是从上述的 3 个流程去优化
(1)提升分词性能
(2)提升分词映射的性能
(3)提升字符串拼接的功能
- 其他方面
(1)空间换时间,比如 cache。这里的 cache 主要可以针对分词+直接的拼音结果。
(2)池化技术,多线程去处理长文本,分段并行处理。
当然这些只是思路,具体还好看测试效果。
一切用数据说话。
特性
丰富
丰富的特性往往和高性能是相悖的,有时候不得不牺牲一些东西。
最好我们的特性都是可配置的,用户使用方便,也可以避免无用的字典载入和对用户无用的计算。
个人觉得主要要有以下几点:
- 转换为拼音
正确性,支持分词。
- 多样化拼音格式
只要首字母(拼音和首字母拼音,其实在中文检索都非常有用)
保留声调,声调也可以用数字,或者声母-韵母的形式标识。
不关心声调。
输入法模式等等
- 汉字转拼音,拼音转汉字
当然如果反转,准确性难以保证。
当然,这个也是可以根据概率推断出来的。
上下文推断感觉一直是传统概率+开发的痛点,有时间还是要学习下神经网络。
-
判断是否为拼音(汉字)
-
用户自定义分词/拼音词库/自定义返回模式
-
繁简体支持
优雅
api 作为开发者的接入点,应该设计的简洁优雅。
当然如何才算是优雅,可能每个人有不同的解读。
api 于开发者,就如同 ui 于用户,其中的设计哲学往往被忽略。
内存占用
基于 utf-8 格式 dict 的词库,优点是可读性非常高,也非常利用后期维护和用户自定义。
缺点也比较明显,占用的内存较多。
压缩词库
类似于 TinyPinyin 的做法,我们用字节的方式存储词库。
当然这里牺牲了可读性。
倒排索引
当然,我们也可以反过来思考,如果觉得内存占用过大,可以放弃将 dict 加载到内存。
而是通过建立倒排索引,然后根据 index 位置去文件检索,当然这种性能会降低。
因为直接访问内存永远是最快的。
cache
再往深处一点想,os 访问磁盘,也会利用 cache。
就算同样是 cahce,也分为 L1 L2 L3。
所以,缓存这个东西由下到上,思想永远是一致的。
如果有时间差,那就可以通过 cache 来进行弥补。
当然,这里永远涉及到时间与空间的衡量(trade-off)问题。
字符串拼接性能篇
众所周知,StringBuilder/StringBuffer 更适合循环的字符串拼接。
那么,还能更快吗?
这个可以借鉴 FastJson 的思想。
主要有两大块:
自行编写类似StringBuilder的工具类SerializeWriter。
把java对象序列化成json文本,是不可能使用字符串直接拼接的,因为这样性能很差。
比字符串拼接更好的办法是使用java.lang.StringBuilder。
StringBuilder虽然速度很好了,但还能够进一步提升性能的,fastjson中提供了一个类似StringBuilder的 com.alibaba.fastjson.serializer.SerializeWriter。
SerializeWriter提供一些针对性的方法减少数组越界检查。
例如 public void writeIntAndChar(int i, char c) {}
,这样的方法一次性把两个值写到buf中去,能够减少一次越界检查。
目前SerializeWriter还有一些关键的方法能够减少越界检查的,我还没实现。
也就是说,如果实现了,能够进一步提升 serialize 的性能。
- 个人收获
这里其实涉及到比较底层的知识,除却 java 中字符串的基本常识,还有一些 StringBuilder 的底层实现原理。
以前学过 C 的同学应该很好理解,String 的底层还是一个 char[],至于这个数组的构建,就看我们如何能尽可能的优化这个构建过程。
使用ThreadLocal来缓存buf。
这个办法能够减少对象分配和gc,从而提升性能。
SerializeWriter 中包含了一个 char[] buf
,每序列化一次,都要做一次分配,使用ThreadLocal优化,能够提升性能。
- 个人收获
ThreadLocal 我们经常用来避免线程的资源争用,这里用来避免多个分配,是一个比较有趣的角度。
原来看过一点点 FastJson 的源码,默认好像是开辟了一个非常大的数组。
按照个人感受来说,我们 99% 的文本应该是少于 1w 字的。
当然,这里需要考虑一些扩容和内存之间的平衡。
TinyPinyin 中的优化思想
以下内容全部来自 TinyPinyin
单字如何判断是否为中文
对于单字符转拼音来说,要解决两个问题:
-
判断传入的字符是否为汉字
-
如果是汉字,则返回它的拼音
在具体解决问题前,首先要深入了解问题本身。
最直观的单字符转拼音方案是维护一张巨大的映射表,存储每一个中文字符对应的拼音,如“中”对应“ZHONG”。
那么中文字符和拼音共多少个呢?
经过简单的统计分析,发现中文字符有如下特征:
-
中文字符共有20378个
-
中文字符除了12295外,均分布在 19968 ~ 40869 之间 (Unicode的4E00 ~ 9FA5),并非连续分布,此范围内夹杂了524个非中文字符
-
拼音共有407个(不包含声调)
根据中文字符和拼音的特征,便可以设计如下的字符转拼音方案:
预先构建19968 ~ 40869的映射表,将每一个char映射为一个拼音(是中文字符)或null(不是中文字符)
判断传入的字符是否为12295,是则返回其拼音
判断传入的字符是否处于19968 ~ 40869之间,不属于则判定不是中文字符;属于的话则查预先构建的映射表,根据查到的值判断是否为中文,并返回相应的结果。
上述方案采用了查表的方法转换拼音,因此速度很快。
然而,映射表的构建往往占据较大的内存,因此需设法降低映射表的空间占用
优化策略
下文将具体阐述TinyPinyin所做的优化。
拼音映射表原始方案
最naive的拼音映射表的结构是:
char --> String // 字符 --> 拼音,如 20013(中) --> "ZHONG"
此方案的劣势非常明显:中文字符共有两万多个,但拼音共有407个,为每个中文字符都分配一个String对象过于浪费空间。
因此,需加以优化。
拼音映射表初步优化
之前统计发现拼音共有407个,那么我们可以分配一个静态的数组保存这407个拼音:
static final String[] PINYIN_TABLE = new String[]{"A", "AI", ...
然后以拼音在数组中的位置作为此拼音的编码,如”A”对应的编码为0,”AI”的编码为1。
拼音映射表便只需存储char对应的拼音编码即可,无需存储拼音本身,大幅降低了内存消耗。
ps: 这里其实是索引的思想应用。
需要注意的是,拼音共407个,因此至少需要9位来表示一个拼音。
Java中byte为8位,short为16位,可采用short来表示一个拼音。
优化后的映射表如下:
char --> short // 字符 --> 拼音编码
内存占用为:short[21000]
存储映射表,共占用42KB内存,存编码的方式比直接存拼音占用空间要小很多。
ps: Short.MAX_VALUE=32767,这句话不太懂什么意思,结合后面反过来推理以下。
然而,我们注意到,编码使用9位就足够了,使用short造成了较大的浪费,每个拼音编码浪费了16 - 9 = 7位,也就是说,理想情况下我们可以将存储所有汉字拼音的42KB内存优化到 42*9/16 = 24KB。
那么如何实现呢?
请见下一步优化。
拼音映射表终极优化
思路是使用 byte[21000] 存储每个汉字的低8位拼音编码,另外采用byte[21000/8]来存储每个汉字第9位(最高位)的编码,每个byte可存储8个汉字的第9位编码。
共耗用内存21KB + 3KB = 24KB,整整降低了42.8%的内存占用。
当然,由于每个编码分为两部分存储,因此解码过程稍微复杂一些,不过采用位运算即可快速的计算出真实的编码:
ps: 这里需要对位运算理解相对深入。
// 计算出真实的编码
short decodeIndex(byte[] paddings, byte[] indexes, int offset) {
int index1 = offset / 8;
int index2 = offset % 8;
short realIndex = (short) (indexes[offset] & 0xff);
if ((paddings[index1] & PinyinData.BIT_MASKS[index2]) != 0) {
realIndex = (short) (realIndex | PinyinData.PADDING_MASK);
}
return realIndex;
}
多音字快速处理方案概览
多音字处理是汉字转拼音库的一个重要特性。
多音字的识别是基于词典实现的,这是由于绝大部分情况下,一个多音字到底该取哪个拼音,是由其所处的词决定的。
例如,对于“重”字,在“重要”一词中应读“ZHONG”,在“重庆”一词中应读“CHONG”。
在TinyPinyin中,对多音字的处理也是基于词典实现的,步骤如下图所示:
向TinyPinyin添加词典
传入待转为拼音的字符串
根据词典,对字符串进行中文分词
单独将分词得到的各个词或字符转为拼音,拼接后返回结果
在整个过程中,最为核心的部分便是分词了。
下面具体介绍分词的处理。
分词方案
基于词典的分词,本质上可分解为两个问题:
一是多串匹配问题。即给定一些模式串(字典中的词),在一段正文中找到所有模式串出现的位置,注意匹配可能有重叠,如”中国人民”可匹配出:[“中国”, “中国人”, “人民”]
二是从匹配到的所有模式串集合中,按照一定的规则挑选出相互没有重叠的模式串子集,以此来得到分词结果,如上例中可挑选出的两种分词结果为:[“中国”, “人民”]和[“中国人”, “民”]
多串匹配算法
TinyPinyin选用了Aho–Corasick算法实现了多串匹配。
Aho-Corasick算法简称AC算法,通过将模式串预处理为确定有限状态自动机,扫描文本一遍就能结束。其复杂度为O(n),即与模式串的数量和长度无关,非常适合应用在基于词典的分词场景。
网上有很多对AC算法原理的介绍,这里不再展开,需要注意的是,AC算法的Java实现有两个流行的版本:AC算法Java实现 和 双数组Trie树(DoubleArrayTrie)Java实现。
后者声称在降低内存占用的情况下,速度能够提升很多,因此TinyPinyin首先集成了此库作为AC算法的实现。
然而,集成后实际使用JProfiler监测发现,双数组Trie树(DoubleArrayTrie)Java实现占用的内存很高,初步分析后发现,AhoCorasickDoubleArrayTrie.loseWeight()中有一些神奇的代码:
/**
* free the unnecessary memory
*/
private void loseWeight()
{
int nbase[] = new int[size + 65535];
System.arraycopy(base, 0, nbase, 0, size);
base = nbase;
int ncheck[] = new int[size + 65535];
System.arraycopy(check, 0, ncheck, 0, size);
check = ncheck;
}
从代码中可以看到,即使是一个空词典,也至少会分配两个 int[65535],共512KB,内存占用实在太高, 因此TinyPinyin最终选用了 Trie树(DoubleArrayTrie) Java实现。
- DoubleArrayTrie
关于 Trie 树,原来个人的分词实现中,使用的也是直接基于 Map 的,内存占用是相对较多。
感觉这个双数组的模式也不错,思想应该是类似于状态机用二维表标识,从而降低内存的消耗。
TreeMap 是用一颗树的形式去标识状态流转。
当然其实性能方面,树查询时间复杂度为 O(lg(t))
,但是双数组时间复杂度为 O(n)
。
作者解释如下:
But most implementation use a TreeMap<Character, State> to store the goto structure, which costs O(lg(t)) time, t is the largest amount of a word's common prefixes. The final complexity is O(n * lg(t)), absolutely t > 2, so n * lg(t) > n . The others used a HashMap, which wasted too much memory, and still remained slowly.
I improved it by replacing the XXXMap to a Double Array Trie, whose time complexity is just O(1), thus we get a total complexity of exactly O(n), and take a perfect balance of time and memory.
Yes, its speed is not related to the length or language or common prefix of the words of a dictionary.
对于 Trie 前缀树数据结构的抽取,可以建立一个框架。
支持各种算法模式:
(1)二维数组
(2)Tree Map
分词选择器
分词选择器的作用是,从匹配到的所有模式串集合中,按照一定的规则挑选出相互没有重叠的模式串子集,以此来得到分词结果。
如上例中可挑选出的两种分词结果为:[“中国”, “人民”]和[“中国人”, “民”]。
常见的分词选择算法包括:正向最大匹配算法、逆向最大匹配算法、双向最大匹配算法等。
TinyPinyin选择了正向最大匹配算法,其基本思路是:从句子左端开始,不断匹配最长的词(组不了词的单字则单独划开),直到把句子划分完。
算法的思想简单直接:人在阅读时是从左往右逐字读入的,正向最大匹配法是与人的习惯相符的。
算法的具体实现请见 ForwardLongestSelector。
算法的输入是匹配到的所有模式串集合(相互之间可能存在重叠),要求输出一个符合最大正向匹配原则的相互没有重叠的模式串子集。
ps: 其实看 nlp 的说法,词库较少,逆向的正确率高于正向。双向结合是最高的。
优秀的 api 设计
基本规则
优秀的API的设计应满足正交性和完备性。
正交性:功能不重叠。
完备性:具有所有用户需要的 api
toPinyin(String original, PinyinMode mode)
这个应该作为核心方法,可以指定返回各种模式的拼音形式。
便捷性
这里提一个用户使用体验问题,我个人就是这种懒人。如果能一行代码搞定,我不想写两行。
如果一个参数可以,我不想传连个参数。
那上面的 api 可以简化,其中 PinyinMode 指定一种最常见的默认形式。
toPinyin(String original, PinyinMode mode)
其他
其他的各种功能,其实不一定是核心。
词典的自定义
文件式
这种相对比较简单,指定用户存放的文件路径即可。
只需要约定好格式,用户甚至不需要关心你的各种 api 设计。
编程式
这种相对需要一定的编程能力,对 api 的设计也有一定要求。
灵活性较好。
如果时间允许,可以二者模式都支持。
TinyPinyin 已经做到极致了吗?
个人认为显然没有。
功能的不完备性
虽然作者看到了大部分情况不需要声调,并且对拼音的内存做了很多优化。
这种思想很好,对于性能和内存的追求,值得每一个开源项目学习。
但作为一个普世的的 pinyin 工具,功能的完备性将大大折扣。
个人希望做一个框架,根据用户指定,获取尽可能高性能与低内存的体验。
如今的 cpu,词典就算加载 10M 对于 jvm 又算了什么?
但是如果用户想要声调功能,只能去换工具了,这显然代价比较大。
这里主要看个人的取舍。
SPI 的可定制化
用过 dubbo 的同学都知道,除却不直接暴露的 api,每一步处理方法都可以认为是一个 spi。
比如:
(1)分词
可以细化为 Trie 的实现。
(2)字典映射
(3)结果拼接
(4)拼音模式处理
这些都应该提供对应的 spi,原因也很简单。
用户可以根据自己的喜好去自定义,因为不同的业务有不同的应用场景。
还有一点就是我们自己的实现往往不是最优的。
同样是分词,基于双数组时间复杂度为 O(n),基于 TreeMap 为 O(log(n)),甚至有 paper 可以对这个过程继续优化。
所以如果对方追求极致的性能,这需要多个部分的协作。
个人比较推崇 spi 的可定制化。
性能
针对性能,TinyPinyin 也有一些可以优化的点。
就我个人而言,目前能看到两个地方:
(1)大文本并行处理
(2)结果 cache 处理
当然这里也是同样的问题,每一个地方都值得深入研究。
拓展阅读
关联框架
trie
cache
string-connector
executor
参考资料
2017-03-19-tinypinyin-part-1.markdown
相关资料
https://github.com/mozillazg/pinyin-data
https://github.com/mozillazg/phrase-pinyin-data
https://blog.csdn.net/sofeware333/article/details/91433540
TinyPinyin
pinyin4j
更多学习
拼音转汉字实现方式
基于HMM的拼音转汉字
这里的拼音一般不带声调。
将汉字作为隐藏状态,拼音作为观测值,使用viterbi算法可以将多个拼音转换成合理的汉字。
例如给出ti,chu,le,jie,jue,fang,an,viterbi算法会认为提出了解决方案是最合理的状态序列。
HMM 需要三个分布,分别是:
-
初始时各个状态的概率分布
-
各个状态互相转换的概率分布
-
状态到观测值的概率分布
这个3个分布就是三个矩阵,根据一些文本库统计出来即可。
viterbi算法基于动态规划,维基百科 - Viterbi algorithm给出了很好的解释和示例。
基于词库的拼音转汉字
基于词库的拼音转汉字
原则:
词的权重大于字的权重;
转换中匹配的词越多,权重越小。
词库的格式是:
拼音:单词:权重
例如:
ni:你:0.15
ni:泥:0.12
a:啊:0.18
hao:好:0.14
nihao:你好:0.6
假如输入是ni,hao,a,我们计算一下各种组合的权重:
组合 权重 | |
---|---|
你,好,啊 | 0.15×0.14×0.18 = 0.00378 |
泥,好,啊 | 0.12×0.14×0.18 = 0.003024 |
你好,啊 | 0.6×0.18 = 0.108 |
可以看出,你好,啊是最好的结果。
实际实现中需要用到动态规划, 和求有向无环图中两点之间最短距离类似。
参考资料
相关资料
https://github.com/aui/pinyin-engine
https://github.com/letiantian/Pinyin2Hanzi
https://github.com/adrianulbona/hmm
汉语拼音是拼写汉民族标准语的拼音方案。
汉语拼音是以北京语音系统作为语音标准的。
北京音也是中国地域最辽阔、人口最多的北方方言的典型代表。
解释汉语拼音用法和标准的《汉语拼音方案》是中国拼音文字方案的国家标准,也是联合国规定用来拼写中国人名地名和专用词语的国际标准。
它是中华人民共和国法定的拼音方案,是世界文献工作中拼写有关中国的专用名词和词语的国际标准。
1958年2月11日,第一届全国人民代表大会第五次会议正式通过了《汉语拼音方案》,并批准公布推行。
《汉语拼音方案》是采用国际通用的拉丁字母,采用音素化的音节结构拼写以北京语音为标准音的普通话的一种方案。
字母表
字母 | 名称 |
---|---|
Aa | ㄚ |
Bb | ㄅㄝ |
Cc | ㄘㄝ |
Dd | ㄉㄝ |
Ee | ㄜ |
Ff | ㄝㄈ |
Gg | ㄍㄝ |
Hh | ㄏㄚ |
Ii | ㄧ |
Jj | ㄐㄧㄝ |
Kk | ㄎㄝ |
Ll | ㄝㄌ |
Mm | ㄝㄇ |
Nn | ㄋㄝ |
Oo | ㄛ |
Pp | ㄆㄝ |
ㄑㄧㄡ | |
Rr | ㄚㄦ |
Ss | ㄝㄙ |
Tt | ㄊㄝ |
Uu | ㄨ |
Vü | ㄪㄝ |
Ww | ㄨㄚ |
Xx | ㄒㄧ |
Yy | ㄧㄚ |
Zz | ㄗㄝ |
声母表
b
p
m
f
d
t
n
l
g
k
h
j
q
x
zh
ch
sh
r
z
c
s
y
w
韵母表
-iㄭzh(ẑ)、ch(ĉ)、sh(ŝ)、r、z、c、s单用时之韵母
i ㄧ 衣
u ㄨ 乌
ü ㄩ 迂
a ㄚ 啊
ia ㄧㄚ 呀
ua ㄨㄚ 蛙
o ㄛ 喔
uo ㄨㄛ 窝
e ㄜ 鹅
ie ㄧㄝ 耶
yoㄧㄛ哟
üe ㄩㄝ 约
ai ㄞ 哀
iaiㄧㄞ 厓
uai ㄨㄞ 歪
ei ㄟ 诶
uei ㄨㄟ 威
ao ㄠ 熬
iao ㄧㄠ 腰
ou ㄡ 欧
iou ㄧㄡ 忧
an ㄢ 安
ian ㄧㄢ 烟
uɑn ㄨㄢ 弯
üan ㄩㄢ 冤
en ㄣ 恩
in ㄧㄣ 因
uen ㄨㄣ 温
ün ㄩㄣ 晕
ang ㄤ 昂
iang ㄧㄤ 央
uang ㄨㄤ 汪
eng ㄥ 亨的韵母;鞥
ing ㄧㄥ 英
ueng ㄨㄥ /u̯əŋ/翁
ong ㄨㄥ /ʊŋ/轰的韵母
iong ㄩㄥ 雍
êㄝ耶之韵母
(1) “知、蚩、诗、日、资、雌、思”等字的韵母用i。
(2) 韵母ㄦ写成er,用做韵尾的时候写成r。
(3 )韵母ㄝ单用的时候写成ê。
(4) i 行的韵母,前面没有声母的时候,写成yi(衣), ya(呀), ye(耶), yao(腰),you(忧),yan(烟),yin(因),yanɡ(央),yinɡ(英),yonɡ(雍)。
u 行的韵母,前面没有声母的时候,写成wu(乌), wa(蛙), wo(窝), wai(歪),wei(威),wan(弯),wen(温),wanɡ(汪),weng(翁)。
ü 行的韵母跟声母j,q,x拼的时候,写成ju(居),qu(区),xu(虚),ü上两点也省略;但是跟声母l,n拼的时候,仍然写成lü(吕),nü(女)。
(5) iou,uei,uen前面加声母的时候,写成iu,ui,un,例如niu(牛),gui(归),lun(论)。
声调
阴平 ˉ
阳平 ˊ
上声 ˇ
去声 ˋ
隔音符号
a,o,e开头的音节连接在其它音节后面的时候,如果音节的界限发生混淆,用隔音符号('
)隔开,例如pi’ao(皮袄)。
发音
平舌音(3个):zi ci si
翘舌音(4个):zhi chi shi ri
三拼音节:ia ua uo uai iao ian iang uang iong
零声母音节: a ai an ang ao e ê ei en eng er o ou
标调规则:有a别放过,没a找o、e,i、u并列标在后,这样标调准没错!
a:发音时,嘴唇自然张大,舌放平,舌头中间微隆,声带颤动。
o:发音时,嘴唇成圆形,微翘起,舌头向后缩,舌面后部隆起,舌居中,声带颤动。
e:发音时,嘴半开,舌位靠后,嘴角向两边展开成扁形,声带颤动。
i:发音时,嘴微张成扁平状,舌尖抵住下齿龈,舌面抬高,靠近上硬腭,声带颤动。
u:发音时,嘴唇拢圆,突出成小孔,舌面后部隆起,声带颤动。
ü:发音时,嘴唇成圆形,接近闭拢,舌尖抵住下齿龈,舌面前部隆起,声带颤动。
b:发音时,双唇紧闭,阻碍气流,然后双唇突然放开,让气流冲出,读音轻短。
p:发音时,双唇紧闭,阻碍气流,然后双唇突然放开,气流迸出成音。
m:发音时,双唇紧闭,舌后缩,气流从鼻腔出来,打开嘴,声带颤动。
f:发音时,上齿触下唇形成窄缝,让气流从缝中挤出来,摩擦成声。
d:发音时,舌尖抵住上牙床,憋住气流后突然放开,气流从口腔迸出,爆发成音。
t:发音时,舌尖抵住上牙床,憋住气后,突然离开,气流从口中迸出。
n:发音时,舌尖抵住上牙床,气流从鼻腔通过,同时冲开舌尖的阻碍,声带颤动。
l:发音时,嘴唇稍开,舌尖抵住上牙床,声带颤动,气流从舌尖两边流出。
g:发音时,舌根前部抵住软腭阻碍气流,让气流冲破舌根的阻碍,爆发成音。
k:发音时,舌根前部,抵住上软腭,阻碍气流,让气流冲破舌根的阻碍,迸发成音。
h:发音时,舌根抬高,接近软腭,形成窄缝,气流从缝中挤出,摩擦成音。
j:发音时,舌尖抵住下门齿,舌面前部紧贴硬腭,气流从窄缝中冲出,摩擦成音。
q:发音时,舌面前部贴住硬腭,气流冲破舌根的阻碍,摩擦成音。
x:发音时,舌尖抵住下门齿,舌面前部抬高靠近硬腭,形成窄缝,气流从缝中挤出,摩擦成音。
zh:发音时,舌尖上翘,抵住硬腭前部,有较弱的气流冲开舌尖阻碍,从缝中挤出,摩擦成音。
ch:发音时,舌尖上翘,抵住硬腭前部,有较强的气流冲开舌尖阻碍,从缝中挤出,摩擦成音。
sh:发音时,舌尖上翘,靠近硬腭前部,留出窄缝,气流从窄缝中挤出,摩擦成音。
r:发音时,舌尖上翘,靠近硬腭前部,留出窄缝,嗓子用力发音,气流从窄缝中挤出,摩擦成音,声带颤动。
z:发音时,舌尖抵住上门齿背,阻碍气流,让较弱的气流冲开舌尖阻碍,从窄缝中挤出,摩擦音。
c:发音时,舌尖抵住上门齿背,阻碍气流,让较强的气流从缝中挤出,摩擦成音。
s:发音时,舌尖接近上门齿背,留出窄缝,气流从舌尖的窄缝中挤出,摩擦成音。
y:发音时,嘴微张成扁平状,舌尖抵住下齿龈,舌面抬高,靠近上硬腭,声带颤动。
w:发音时,嘴唇拢圆,突出成小孔,舌面后部隆起,声带颤动。
ai:发音时,先发 a 的音,然后滑向i,气流不中断,读音轻短。
ei:发音时,先发 e 的音,然后滑向i,气流不中断,嘴角向两边展开。
ui:发音时,u 的发音轻短,然后滑向ei,嘴形由圆到扁。
ao:发音时,先发 a 的音,然后舌尖后缩,舌根向上抬,嘴形拢成圆形,轻轻的滑向 o。
ou:发音时,先发 o 的音,嘴唇渐收拢,舌根抬高,口型由大圆到小圆。
iu:发音时,先发 i,然后向ou滑动,口型由扁到圆。
ie:发音时,先发 i,再发e,气流不中断。
üe:发音时,先发 ü 的音,然后向e滑动,口型由圆到扁。
er:发音时,舌位居中发 e 的音,然后舌尖向硬腭卷起,两个字母同时发音。
an:发音时,先发 a 的音,然后舌尖逐渐抬起,顶住上牙床发n的音。
en:发音时,先发 e 的音,然后舌面抬高,舌尖抵住上牙床,气流从鼻腔泄出,发n的音。
in:发音时,先发 i 的音,然后舌尖抵住下门齿背,舌面渐至硬腭,气流从鼻腔泄出,发en的音。
un:发音时,先发 u 的音,然后舌尖抵住上牙床,接着发en的音,气流从鼻腔泄出。
ün:发音时,先发 ü 的音,然后舌头上抬,抵住上牙床,气流从鼻腔泄出,发en的音。
ang:发音时,先发 a 的音,然后舌根抵住上软腭,气流从鼻腔泄出,发后鼻音尾ng的音。
eng:发音时,先发 e 的音,然后舌尖抵住下牙床,舌根后缩抵住软腭发ng音,气流从鼻腔泄出。
ing:发音时,舌尖触下齿龈,舌面隆起至硬腭,鼻腔共鸣成声。
ong:发音时,先发 o 的音,然后舌根后缩抵住软腭,舌面隆起,嘴唇拢圆,鼻腔共鸣成声。
注意事项
拼写需注意的事项:
-
j、q、x、y遇到ü ,两个小点要拿去
-
句子开头的首字母要大写;汉语人名的开头字母要大写;专有名词的开头字母要大写例:Beijing;文章标题开头字母要大写。
参考资料
pinyin4j 之中文拼音的基础知识
拼音声调
声调
拼音声调是指普通话中的声调,通常叫四声,即阴平(第一声),用“ˉ”表示,如lā;阳平第二声,用“ˊ”表示,如lá;上声(第三声),用“ˇ”表示,如lǎ;去声(第四声),用“ˋ”表示,如;là。
汉语中还存在着一种特殊声调,叫做轻声,有时也叫第五声,在汉语拼音中不标调。有些学者认为“第五声”的说法并不确切。轻声虽然能够起分辨语义的作用,但是通常不列入汉语“四声”之一,因为声调是正常重音音节的音高形式。在音高上,轻音只有音区特征,声调还有曲拱特征。
每个汉字由韵母和声母配合构成一个音节构成。在韵母上部应该标出声调,为了方便也可省略。声调影响舌头位置,不仅仅声带有关。
标音调的问题
汉语拼音中标声调位置的规则如下:
若有两个韵母(元音),且第一个韵母(元音)为i、u、或是ü时,则将声调标示在第二个韵母(元音)上。
其余状况下声调皆应标示于第一个韵母(元音)之上。
汉语拼音方案
历史
这里不再赘述,语同音,字同形的意义其实比文字本身要大的多。
方案
参见 汉语拼音方案
语音标注方案
符号标调
《汉语拼音方案》中声调采用的是符号标调:
1957年11月1日国务院全体会议第六十次会议通过,1958年2月11日第一次全国人民代表大会第五次会议批准的《汉语拼音方案》中声调符号采用的是:阴平(ˉ)、阳平(ˊ)、上声(ˇ)去声(ˋ)、轻声(不标调)的方法。这种方法解决了不同声调汉字的区别问题。例如,妈 mā(阴平)、麻 má(阳平)、马 mǎ(上声)、骂 mà(去声)、吗 mɑ(轻声不标调)。
为识字辨音立下了汗马功劳。
但随着信息化的不断发展,其与计算机键盘不相适应。输入1个声调符号要折腾好一阵子,用起来很不爽快,影响汉字信息化的不断发展。
数字标调
采用数字 1、2、3、4、5,代替《汉语拼音方案》中声调阴平(ˉ),阳平(ˊ),上声(ˇ),去声(ˋ),轻声(不标调)这几个标调符号。
实施方法:
(1)用数字1代替阴平(ˉ)符号,因为阴平为第一声。例如,拼音pīn yīn,按本方法是pin1 yin1。
例字:吖 呵 阿 啊 锕 腌的拼音是“ā”。用本方法,吖 呵 阿 啊 锕 腌的拼音是“a”。
哀 挨 埃 唉 哎 锿的拼音是“āi”。用本方法,哀 挨 埃 唉 哎 锿的拼音是“ai1”。
(2)用数字2代替阳平(ˊ)符号,因为阳平为第二声。
例字:挨 癌 皑 捱的拼音是“ái”。用本方法,挨 癌 皑 捱的拼音是“ai2”。
(3)用数字3代替上声(ˇ)符号,因为上声为第三声。
例字:毐 矮 蔼 霭的拼音是“ǎi”。用本方法,毐 矮 蔼 霭的拼音是“ai3”。
(4)用数字4代替去声(ˋ)符号,因为去声为第四声。
例字:艾 哎 砹 唉爱 嗳 暧 瑷 嫒 碍 隘 嗌的拼音是“ài”。用本方法,艾 哎 砹 唉爱 嗳 暧 瑷 嫒 碍 隘 嗌的拼音是“ai4”。
(5)用数字5代替轻声,因为轻声为第5声。
例字:啊字的拼音是“ɑ”。用本方法,啊字的拼音是“a5”。吧 罢字的拼音是“ba”。用本方法,吧 罢字的拼音是“ba5”。
(6)说明:按上述实施方法,用数字标调,一律将声调(数字)标示在音节后面。
例如:成立 cheng2 li4
标调的位置
标调就是按一定规则给音节标上调号,表示这个音节读第几声。
汉语声调符号的标记位置有两种情况:
a母出现不放过, (即韵母中凡是有a的,标在a上。如lao,标在a上)
没有a母找 o e , (没有a,但有o 或e的,标在 o 或e 上。如lou标在o上,lei标在e上)
i u并列标在后, (i和 u并列时,标在后面。比如liu,标在u上,gui,标在i 上)
单个韵母不必说。 (单个的韵母,当然就标它上面了)
参考资料
https://github.com/mozillazg/pinyin-data
https://github.com/mozillazg/phrase-pinyin-data