在Android系统设备中,如果有包含简体中文或繁体中文标题的歌曲时,有时候会看到乱码的现象,这是怎么回事?
要想知道答案,需要先了解下字符编码相关知识。
字符乱码问题由来
PC出现的早期,不同国家或区域对自己的文字制定了编码规范,大家各自为政,没有标准化示例:
编码方案A: 代码 100 内容:”###“
编码方案B: 代码 100 内容:”@@@“
问题出现了:将编码方案A编码的内容使用在编码方案B中,会显示预期之外的内容如乱码等。
在此例子中,如果某文字用方案A编码,但是用方案B解码,则本应该显示”###“的文字会变成”@@@“
如何解决编码问题?
为解决各地区不同标准产生的编码问题,需要一种统一的编码方式,这便是ISO 10646和Unicode的由来。
各种不同的编码
1. ISO 10646ISO 10646标准由国际标准化组织ISO颁布,用来实现全球所有文种的统一编码;
ISO 10646定义了标准字符集(Universal Character Set,UCS);
历史上存在两个独立的尝试创立单一字符集的组织,即国际标准化组织(ISO)和多语言软件制造商组成的统一码联盟即Unicode。
前者开发了ISO/IEC 10646 项目,后者开发了统一码项目。因此最初制定了不同的标准。
1991年前后,两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。
2.Unicode
Unicode:统一码,为每一个字符定义唯一的代码(即一个整数);
目前的 Unicode 字符分为 17 组编排, 每组称为平面(Plane),而每平面拥有65536个代码点;
UCS-2即Unicode 0号平面字符集;
Unicode字符平面映射 http://zh.wikipedia.org/wiki/Unicode%E5%AD%97%E7%AC%A6%E5%B9%B3%E9%9D%A2%E6%98%A0%E5%B0%84
Unicode 编码表 http://zh.wikibooks.org/wiki/Unicode
Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称为UTF),如UTF-16,UTF-8等;
UTF-8:
8-bit Unicode Transformation Format,是一种针对Unicode的可变长度字符编码;
UTF-8使用一至四个字节为每个字符编码:
- ASCII字符只需一个字节编码;
- 拉丁文等 需要二个字节编码;
- 其他基本多文种平面(BMP)中的字符三字节编码
- 其他极少使用的Unicode 辅助平面的字符使用四字节编码
Unicode和UTF-8之间的转换关系表
UCS-4编码 UTF-8字节流
U+00000000 – U+0000007F 0xxxxxxx
U+00000080 – U+000007FF 110xxxxx 10xxxxxx
U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
U+00010000 – U+001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
UTF-16:
16bit Unicode Transformation Format,把Unicode的码位转换为16比特长的码元;
UTF-16是UCS-2的父集,在BMP中,UTF-16编码就等于UCS码;
在辅助平面中,UTF-16使用“代理对”的方法进行编码;
代理对编码方法:
1.码位减去0x10000, 得到的值的范围为20比特长的0..0xFFFFF.
2.高位的10比特值加上0xD800,得到高位代理(范围0xD800..0xDBFF);
3.低位的10比特值加上0xDC00,得到低位代理(范围0xDC00..0xDFFF);
高位代理、低位代理、BMP中的有效字符的码位,三者互不重叠;
字节序:
- 不同平台使用不同字节序;
- 为了确定UTF-16文件的大小尾序,在采用UTF-16编码文件的开头,都会放置BOM(Byte Order Mark)信息,FF FE代表UTF-16LE,FE FF代表UTF-16BE;
- U+FEFF,U+FFFE字符在UNICODE中代表的意义是ZERO WIDTH NO-BREAK SPACE,它是个没有宽度也没有断字的空白;
- U+6731,UTF-16LE编码为“31 67”,UTF-16BE编码为“67 31”;
3.ASCII
American Standard Code for Information 美国信息交换标准代码;
单字节编码,使用7位二进制数表示,编码范围为0 - 127,可表达128个字符;
ASCII码字符不存在乱码问题,因为绝大部分地区编码都兼容ascii;
扩展ASCII:
标准ASCII码只用到一个字节中的7位,如果使用第8位,则可扩展编码范围128 - 255,扩展后的标准即为ISO8859-1,也称为 Latin-1
4. ISO8859
- ISO 8859,全称ISO/IEC 8859,是一个8位字符集的标准,现时定义了15个字符集
- ASCII收录了空格及94个“可印刷字符”,足以给英语使用。但是,其他使用拉丁字母的语言(主要是欧洲国家的语言),都有一定数量的附加符号字母,故可以使用ASCII及控制字符以外的区域来储存及表示。
- 兼容ASCII,0x20 - 0x7F为ASCII,0xA0-0xFF为ISO 8859-x字符
- Unicode 0x00-0xFF范围的字符由 0x00-0x7F(ASCII) + 0x80-0x9F(控制符) + 0xA0-0xFF(ISO 8859-1)组成
各种ISO 8859字符集
ISO/IEC 8859-1 (Latin-1) - 西欧语言
ISO/IEC 8859-2 (Latin-2) - 中欧语言
ISO/IEC 8859-3 (Latin-3) - 南欧语言。世界语也可用此字符集显示。
ISO/IEC 8859-4 (Latin-4) - 北欧语言
ISO/IEC 8859-5 (Cyrillic) - 斯拉夫语言
ISO/IEC 8859-6 (Arabic) - 阿拉伯语
ISO/IEC 8859-7 (Greek) - 希腊语
ISO/IEC 8859-8 (Hebrew) - 希伯来语(视觉顺序)
ISO 8859-8-I - 希伯来语(逻辑顺序)
ISO/IEC 8859-9(Latin-5 或 Turkish)- 它把Latin-1的冰岛语字母换走,加入土耳其语字母。
ISO/IEC 8859-10(Latin-6 或 Nordic)- 北日耳曼语支,用来代替Latin-4。
ISO/IEC 8859-11 (Thai) - 泰语,从泰国的 TIS620 标准字集演化而来。
ISO/IEC 8859-13(Latin-7 或 Baltic Rim)- 波罗的语族
ISO/IEC 8859-14(Latin-8 或 Celtic)- 凯尔特语族
ISO/IEC 8859-15 (Latin-9) - 西欧语言,加入Latin-1欠缺的芬兰语字母和大写法语重音字母,以及欧元(€)符号。
ISO/IEC 8859-16 (Latin-10) - 东南欧语言。主要供罗马尼亚语使用,并加入欧元符号。
由于英语没有任何重音字母(不计外来词),故可使用以上十五个字集中的任何一个来表示。
6.代码页
代码页(Code Page),也称“内码表”,是特定语言的字符集的一张表;
早期,字符集编码信息存放在ROM中,被称为OEM代码页(IBM PC使用);
微软针对不同的使用地区与国家,定义了一系列的支持不同语言字符集的代码页,被称作"Windows (或ANSI) 代码页";
不同的厂商对同一个字符集编码使用各自不同的名称。例如,UTF-8在IBM称作代码页1208, 在微软称作代码页65001, 在SAP称作代码页4110;
微软系统中,中日韩语言代码页:
932 — 日文 (Shift-JIS)
936 — 简体中文(GBK)
949 — 韩文 (EUC-KR)
950 — 繁体中文(Big5)
GB-x:简体中文编码
GB2312:
中华人民共和国国家标准简体中文字符集,共收录6763个汉字,覆盖中国大陆99.75%的使用频率
对于人名、古汉语等方面出现的罕用字,GB 2312不能处理每个汉字及符号以两个字节来表示,兼容ASCII
Unicode BMP平面汉字。1993年,Unicode 1.1版本推出,收录中国大陆、台湾、日本及韩国通用字符集的汉字,总共有20,902个。
中国大陆将之定为GB13000
汉字内码扩展规范,K为汉语拼音 Kuo Zhan(扩展)中“扩”字的声母
相当于GB 2312 + GB 13000。由于GB 2312-80只收录6763个汉字,有不少汉字并未有收录在内,于是厂商微软利用GB 2312-80未使用的编码空间,收录GB13000.1-93全部字符制定了GBK编码字符有一字节和双字节编码。对于单字节,00–7F范围即ASCII,对于双字节,第一字节的范围是81–FE,第二字节的范围在40–7E及80–FE
国家标准GB 18030-2005《信息技术 中文编码字符集》,是中华人民共和国现时最新的内码字集
与GB 2312-1980完全兼容,与GBK基本兼容,支持GB 13000及Unicode的全部统一汉字,共收录汉字70244个
Big5:繁体中文编码
Big5,又称为大五码或五大码,繁体中文地区中最常用的汉字字符集,共收录13,060个汉字
双字节字符集,“高位字节”使用了0xA1-0xF9,“低位字节”使用了0x40-0x7E,及0xA1-0xFE (CP950)
由于很多日常用字未被收录(“着”,“柏”,“喆”等),所以在市面上支持Big5码的软件(如仓颉输入法),有不少都自行在原本的编码外,添加一些符号及用字
编码检测
如何知道当前字符是何种编码?不同代码页使用的代码范围不一样,通过检测代码范围来得到一个字符可能的编码。
注意,中日韩等象形文字的编码范围有可能会有重叠部分,因此某个字符检测可能得到多个结果。
如果是判断某个字符串,则可检测每个字符可能的编码,对每一个结果进行与操作,则可得到更准确的结果。
编码 范围(只有部分信息,实际请参考代码页)
Shift-JIS 高位字节0x81-0xFC,低位字节0x40-0x7E,…,0x -0xFC,
GBK 高位字节0x81-0xFE,低位字节0x40-0x7E,0x80-0xFE
Big5 高位字节0xA1-0xF9,低位字节0x40-0x7E,0xA1-0xFE
EUC-KR 高位字节0x81-0xFD,低位字节0x41-0x5A,0x61-0x7A,0x81-0xFE
注意编码范围不是连续的,因此在实现过程中,需要根据代码页把其所有范围列出来,检测编码时判断字符是否在该范围
Android使用 微软的代码页:http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/
例:
GBK编码范围
{
{0x8140,0x817E},
{0x8180,0x81FE},
{0x8240,0x827E},
...
}
编码转换
ICU:International Components for Unicode, Unicode国际化组件;
官网:http://site.icu-project.org/
ICU主要功能:
代码页转换
字符比较器(Collation)
日期,时间,货币,数字等格式转换
时区计算
Unicode支持
正则表达式
处理文本排向(Bidi )
文本边界
ICU提供一个转换器,可以将代码页编码(如GBK)转换成Unicode编码
同一种编码可能有多个别名,创建转换器时可以输入别名
转换器别名信息可从android源码中获取,路径为: android/external/icu4c/data/mappings/convrtrs.txt
或者从 该地址查看Demo: http://demo.icu-project.org/icu-bin/convexp
使用ICU4C完成不同编码的转换
步骤:
1.创建转换器
convDest = ucnv_open(name,…); //目标转换器,打开转换器如”GBK”,”UTF-8”,返回converter对象
convSrc = ucnv_open(name,…); //源转换器
2.转换
ucnv_convertEX(convDest,convSrc,pDest,pDestLen,pSrc,pSrcLen,…)
3.关闭转换器
ucnv_close(convDest);
ucnv_close(convSrc);
结论
了解了字符编码相关知识后,再来看乱码现象。目前智能手机,平板等设备基本使用unicode字符集,对于unicode字符,可以正常显示,而非unicode字符则需要先转换成unicode,否则会显示乱码。
在转换之前,需要检测编码。由于中日韩文字的编码有重叠部分,检测时有可能得到多个结果,因此在检测结果上还要加个条件,即根据当前设置的语言来决定最终结果。
所以如果设置当前语言为简体中文,查看繁体中文或日韩文编码信息的歌曲时会看到乱码。
抛开效率问题,这种情况其实可以解决,通过判断检测结果是否唯一,如果是则将对应编码转换成utf8即可。
有些mp3歌曲信息是unicode编码,所以无论设置哪种语言都可以显示正确。