字符集与编码方式解惑

1、什么是【locale】?

locale】指一个程序或者操作系统所处的国家、字符集、时间显示的格式、货币符号等。

在C标准库的locale.h文件中定义了常量LC_ALL(全部locale项)、LC_COLLATE(排序)、LC_CTYPE(语言符号)、LC_MESSAGES、LC_MONETARY(货币符号)、LC_NUMERIC、LC_TIME(日期格式)等。locale是linux系统和C语言的称呼,在windows中它被称为【区域和语言】。linux系统提供了locale命令来修改系统的【locale】,windows提供了图形界面来修改【区域和语言】。

 

2、保存txt文件时选择编码?

“另存为”对话框的下方→编码→选择ANSI/Unicode/Unicode big edian/UTF-8。这里的编码指的就是【字符集】,字符集只是locale设置的一部分。

 

3、修改【区域和语言】?

windows系统中,【控制面板】→【时钟、语言和区域】→【区域和语言】→【管理】→【更改区域系统设置】。

 

4、【字符集】和【编码方式】的区别?

字符集给出的是字符和数字的映射,但是不一定被作为机器实现,而编码方式给出的是字符和01序列的映射,指机器实现。一般一个字符集等同于一个编码方式,ANSI体系的字符集如ASCII、ISO 8859-1、GB2312、GBK等等都是如此。一个字符集上也可以有多种编码方式,例如UCS字符集上有UTF-8、UTF-16、UTF-32等编码方式。

 

常见【编码方式】:

注:

ANSI编码体系和Unicode编码体系的区别是:ANSI中的编码方式如GBK,JIS都只包含一个国家的字符,而不能包含全世界的字符,【本地化】的痕迹很重,无法解决乱码问题,而Unicode中的编码方式建立在UCS的基础上,是【国际化】的,没有乱码问题。不过在我看来,Unicode的精髓应该是:(1)所有字符在机器中的存储是等宽的(2)所有字符存储的都是UCS编号。其实,“将世界上所有的字符都编入一个字符集(UCS就是这样一个字符集),然后存储字符在该字符集中的编号”,这是一个多么自然的想法!可是历史就是这么悲剧,偏偏要出现ASCII,出现GBK,出现各个国家的编码方式,导致现在写程序还要setlocale。

根据上面两点Unicode精髓,真正称得上Unicode编码方式的只有UTF-32UCS-2(UCS-2是UTF-16的BMP平面,即UCS中的前65536个字符),因为它存储的是字符在UCS中的编号,不像GBK玩弄一些在最高位设为1来显示汉字的技巧,也不像UTF-8为了不节省空间而使用压缩编码。在业界常常说的“采用Unicode编码”说的就是UTF-16的BMP平面,所谓“宽字节字符集”也就是BMP平面,Java中“用两个字节存储一个字符”也是如此。所以,“采用Unicoe编码”=UCS-2=“用两个字节存储一个字符”=“宽字节字符集

 

(1)ANSI编码体系

ASCII :含128个字符,在计算机中用一个字节表示,所有字符的码值最高位为0。

ISO 8859-1:ASCII的扩展,含256个字符,一个字节。

GB2312:国标2312字符集,含94*94个字符,汉字占6763个,两个字节,汉字码值的高字节最高位和低字节最高位都是1。

GBK:GB2312的扩展,加入了不常用汉字和繁体汉字。

GB18030:2005年的新标准,官方强制使用GB 18030标准,但较旧的计算机仍然使用GB 2312。

BIG5:台湾地区的字符集,繁体字。

JIS:日本的字符集。

 

(2)Unicode编码体系

UCS、UCS-4和UTF-32:UCS是Unicode编码体系所采用的字符集,以4字节表示一个字符。在机器实现中采用4个字节来表示一个字符,将造成极大地空间浪费,所以一般UCS不被推荐使用,UCS也叫UCS-4和UTF-32。

 

UTF-8:一般不认为UTF-8是字符集,而是在UCS基础上进行二次编码的一种编码方式,因而属于Unicode编码体系。UTF是Unicode Transformation Format的缩写。顺便说一下,上面说的【字符集】都同时是【编码方式】。因为考虑到UCS的空间浪费,UTF-8将UCS中的字符分为6类,用类似于huffman编码的方式编码,从而使不同的字符编码长度不同,出现频率高的字符编码长度短。

 

UTF-16与UCS-2: UTF-16可看成是UCS-2的父集。在没有辅助平面字符surrogate code points前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后,就称为UTF-16了。现在若有软件声称自己支持UCS-2编码,那其实是暗指它不能支持在UTF-16中超过2bytes的字集。对于小于0x10000的UCS码,UTF-16编码就等于UCS码。

 

 

5、Unicode的历史

UnicodeASCII是兼容的,如ASCII的0X76的UNICODE编码就是0X0076。历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。

 

6、ANSI常常被作为属于它的某个编码方式的代称。

GB2312,BIG5,JIS等只是【ANSI编码体系】中的一种编码方式。在设置了某个编码方式的系统中,ANSI指的就是那个编码方式。例如在将【区域与语言】设置为GB2312的windows系统中,ANSI指的就是GB2312;而在JIS编码的windows系统中,ANSI指的就是JIS;在英文版的windows系统中,ANSI指的就是ASCII或者ISO 8859-1。

实际使用例子:在windows记事本的另存为对话框中有“编码”选项,其中就有ANSI,由于我的windows系统的【区域与语言】是GB2312,所以ANSI指的就是GB2312。

 

7、国际化与本地化:

人们常把I18N作为“国际化”的简称,其来源是英文单词 internationalization的首末字符i和n。18为中间的字符数。国际化是指在设计软件时,将软件与特定语言及地区脱钩的过程。当软件被移植到不同的语言地区时,软件本身不用做内部工程上的改变或修正。本地化则是指当移植软件时,加上与特定区域设置有关的资讯和翻译文件的过程。 国际化和本地化之间的区别虽然微妙,但却很重要。国际化意味着产品有适用於任何地方的潜力;本地化则是为了更适合於特定地方的使用,而另外增添的特色。用一项产品来说,国际化只需做一次,但本地化则要针对不同的区域各做一次。 这两者之间是互补的,并且两者结合起来才能让一个系统适用於各地。在如微软及IBM等企业中,则会使用全球化(globalization)来表示此两者的合称,在英文中,也会使用 g11n做为简称。

l10n:localization

i18n:internalization

g11n:globalization

i18n+l10n=g11n(国际化+本地化=全球化)

 

8、UTF-8 http://learn.akae.cn/media/apas02.html

为了统一全世界各国语言文字和专业领域符号(例如数学符号、乐谱符号)的编码,ISO制定了ISO 10646标准,也称为UCS(Universal Character Set)。UCS编码的长度是31位,可以表示231个字符。如果两个字符编码的高位相同,只有低16位不同,则它们属于一个平面(Plane),所以一个平面由216个字符组成。目前常用的大部分字符都位于第一个平面(编码范围是U-00000000~U-0000FFFD),称为BMP(Basic Multilingual Plane)或Plane 0,为了向后兼容,其中编号为0~256的字符和Latin-1相同。UCS编码通常用U-xxxxxxxx这种形式表示,而BMP的编码通常用U+xxxx这种形式表示,其中x是十六进制数字。在ISO制定UCS的同时,另一个由厂商联合组织也在着手制定这样的编码,称为Unicode,后来两家联手制定统一的编码,但各自发布各自的标准文档,所以UCS编码和Unicode码是相同的。

有了字符编码,另一个问题就是这样的编码在计算机中怎么表示。现在已经不可能用一个字节表示一个字符了,最直接的想法就是用四个字节表示一个字符,这种表示方法称为UCS-4或UTF-32,UTF是Unicode Transformation Format的缩写。一方面这样比较浪费存储空间,由于常用字符都集中在BMP,高位的两个字节通常是0,如果只用ASCII码或Latin-1,高位的三个字节都是0。另一种比较节省存储空间的办法是用两个字节表示一个字符,称为UCS-2或UTF-16,这样只能表示BMP中的字符,但BMP中有一些扩展字符,可以用两个这样的扩展字符表示其它平面的字符,称为Surrogate Pair。无论是UTF-32还是UTF-16都有一个更严重的问题是和C语言不兼容,在C语言中0字节表示字符串结尾,库函数strlen、strcpy等等都依赖于这一点,如果字符串用UTF-32存储,其中有很多0字节并不表示字符串结尾,这就乱套了。

UNIX之父Ken Thompson提出的UTF-8编码很好地解决了这些问题,现在得到广泛应用。UTF-8具有以下性质:

  • 编码为U+0000~U+007F的字符只占一个字节,就是0x00~0x7F,和ASCII码兼容。
  • 编码大于U+007F的字符用2~6个字节表示,每个字节的最高位都是1,而ASCII码的最高位都是0,因此非ASCII码字符的表示中不会出现ASCII码字节(也就不会出现0字节)。
  • 用于表示非ASCII码字符的多字节序列中,第一个字节的取值范围是0xC0~0xFD,根据它可以判断后面有多少个字节也属于当前字符的编码。后面每个字节的取值范围都是0x80~0xBF,见下面的详细说明。
  • UCS定义的所有231个字符都可以用UTF-8编码表示出来。
  • UTF-8编码最长6个字节,BMP字符的UTF-8编码最长三个字节。
  • 0xFE和0xFF这两个字节在UTF-8编码中不会出现。

具体来说,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
U-00200000 – U-03FFFFFF:  111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 – U-7FFFFFFF:  1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

第一个字节要么最高位是0(ASCII字节),要么最高两位都是1,最高位之后1的个数决定后面有多少个字节也属于当前字符编码,例如111110xx,最高位之后还有四个1,表示后面有四个字节也属于当前字符的编码。后面每个字节的最高两位都是10,可以和第一个字节区分开。这样的设计有利于误码同步,例如在网络传输过程中丢失了几个字节,很容易判断当前字符是不完整的,也很容易找到下一个字符从哪里开始,结果顶多丢掉一两个字符,而不会导致后面的编码解释全部混乱了。上面的格式中标为x的位就是UCS编码,最后一种6字节的格式中x位有31个,可以表示31位的UCS编码,UTF-8就像一列火车,第一个字节是车头,后面每个字节是车厢,其中承载的货物是UCS编码。UTF-8规定承载的UCS编码以大端表示,也就是说第一个字节中的x是UCS编码的高位,后面字节中的x是UCS编码的低位。

例如U+00A9(©字符)的二进制是10101001,编码成UTF-8是11000010 10101001(0xC2 0xA9),但不能编码成11100000 10000010 10101001,UTF-8规定每个字符只能用尽可能少的字节来编码。

 

 

9、Java中一个字符占两个字节。”

据说Java中的一个字符占两个字节,而且采用的是Unicode编码,到底采用的是UTF-8、UTF-16还是UTF-32呢?

是UTF-16,更准确地说,是UTF-16的子集,因为UTF-16能够表示Unicode编号超过65535的字符,Java中却不能!

(官方的解释:“The Java programming language is based on the Unicode character set, and several libraries implement the Unicode standard. The primitive data type char in the Java programming language is an unsigned 16-bit integer that can represent a Unicode code point in the range U+0000 to U+FFFF, or the code units of UTF-16.”)

很明显“一个字符占两个字节”就表示Java中最多只能表示65536个字符,而不是UCS字符集那么多。所谓“采用Unicode编码”指的是Java存储的是字符在Unicode中的对应的编号,那么Java就无法表示那些编号超过0xFFFF(65535)的字符。例如,“中”字的Unicode编号是4E2D,将其化为01序列则是01001110  00101101,在UTF-8中,它的码值是

11100100 10111000 1011101 ,如果按照UTF-8来存储,应该用三个字节存,但事实是,Java存的是前一个01序列。记住,Java存的是Unicode编号。应该说,宽字节字符在存储时(无论是windows系统还是linux系统,是C#语言还是Java语言)都是用Unicode编号。

 

问题1:

我们在VC中随便写两行和字符串常量有关的代码,例如:

char *pchar=”你好”;

cout<<pchar<<endl;

好学的你发问,”你好”这两个汉字在计算机中是怎样的01序列呢?这和程序运行时所处的操作系统有关,比如我的系统是windows 7且【区域和语言】被设置为”中文简体”即GB2312。根据GB2312的字符集,中文是两个字节的,“你好”分别是0xC4E3和0xBAC3,所以存储在计算机中的是这两个值。

cout<<pchar;为什么能够正确输出中文?

中文版DOS窗口的code page 为GB2312,即默认字符集为GB2312。而在GB2312编码中,所有字符都是两个字节,用内码来表示的话每个字节的值都大于127,单个字节属于不可打印字符,当控制台遇到这样两个字节时,会把它们作为一个汉字输出,这个过程是由控制台来完成,对于程序来说是透明的。这是很久以前中文DOS系统采用的技术。

 

问题2:

打开文件时看到乱码的原因:文件本身不包含编码信息,是【区域和语言】决定了程序打开文件时读取的规则,例如某文件的前四个字节是A1B2C3D4,在一种语言区域选项下,程序可能会将四个字节逐个解析,产生四个字符,在另一种【区域和语言】下,程序可能会将四个字节以两个为一对进行解析,产生两个字符。

不产生乱码的条件是:保存文件时所采用的【编码方式】和打开文件时所采用的【编码方式】是一样的。例如,对于同样的“中文”两个汉字,对GB2312和BIG5两个不同的代码页,在保存时被存储为不同的01序列。所以,打开的时候也必须使用同样的代码页才能够解读。

 

10、参考文献:

[1]完整论述字符编码 http://www.regexlab.com/zh/encoding.htm

[2]ISO 8859-1编码方式http://zh.wikipedia.org/wiki/ISO/IEC_8859-1

[3]Unicode和UTF-8详述http://learn.akae.cn/media/apas01.html

[4]setlocale函数的参数:代码页 http://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx

[5]ANSI和Unicode区别http://www.360doc.com/content/10/0712/17/11192_38530531.shtml

[6]谈谈Unicode编码 http://pcedu.pconline.com.cn/empolder/gj/other/0505/616631_1.html

[7]四个版本的国标字符集说明:http://faq.comsenz.com/viewnews-534

[8]alt+code输入任意字符:http://hi.baidu.com/ybbmdf/blog/item/578f6b061098f768020881cc.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值