从计算机对多国语言的支持角度看,大体能够分为三个阶段:程序员
系统内码 | 说明 | 系统 | |
阶段一 | ASCII | 计算机刚开始只支持英语,其它语言不可以在计算机上存储和显示。 | 英文 DOS |
阶段二 | ANSI编码 (本地化) |
为使计算机支持更多语言,一般使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。好比:汉字 '中' 在中文操做系统中,使用 [0xD6,0xD0] 这两个字节存储。 不一样的国家和地区制定了不一样的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来表明一个字符的各类汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码表明 GB2312 编码,在日文操做系统下,ANSI 编码表明 JIS 编码。 不一样 ANSI 编码之间互不兼容,当信息在国际间交流时,没法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。 |
中文 DOS,中文 Windows 95/98,日文 Windows 95/98 |
阶段三 | UNICODE (国际化) |
为了使国际间信息交流更加方便,国际组织制定了 UNICODE 字符集,为各类语言中的每个字符设定了统一而且惟一的数字编号,以知足跨语言、跨平台进行文本转换、处理的要求。 | Windows NT/2000/XP,Linux,Java |
字符串在内存中的存放方法:网络
在 ASCII 阶段,单字节字符串使用一个字节存放一个字符(SBCS)。好比,"Bob123" 在内存中为:ide
42 | 6F | 62 | 31 | 32 | 33 | 00 |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
B | o | b | 1 | 2 | 3 | \0 |
在使用 ANSI 编码支持多种语言阶段,每一个字符使用一个字节或多个字节来表示(MBCS),所以,这种方式存放的字符也被称做多字节字符。好比,"中文123" 在中文 Windows 95 内存中为7个字节,每一个汉字占2个字节,每一个英文和数字字符占1个字节:函数
D6 | D0 | CE | C4 | 31 | 32 | 33 | 00 |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
||
中 | 文 | 1 | 2 | 3 | \0 |
在 UNICODE 被采用以后,计算机存放字符串时,改成存放每一个字符在 UNICODE 字符集中的序号。目前计算机通常使用 2 个字节(16 位)来存放一个序号(DBCS),所以,这种方式存放的字符也被称做宽字节字符。好比,字符串 "中文123" 在 Windows 2000 下,内存中实际存放的是 5 个序号:工具
2D | 4E | 87 | 65 | 31 | 00 | 32 | 00 | 33 | 00 | 00 | 00 | ← 在 x86 CPU 中,低字节在前 |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
||||||
中 | 文 | 1 | 2 | 3 | \0 |
一共占 10 个字节。编码
2: 字符,字节,字符串spa
字节:8位byte为一字节,它是计算机中存储数据的单元,占8位二进制数。操作系统
字符:根据编码方式的不一样,字符所占的字节个数也不一样,如上例所述。code
ASSCII:一个字符占用一个字节内存
ANSI:一个字符能够占一个或多个字节,好比,a占一个字节,而中文字符通常占二个字节
UNICODE:一个字符占二个字节,对于英文字符也是,可经过低字节填充来实现
字符串:它由字符组成
3:字符集和编码
使用哪些字符。也就是说哪些汉字,字母和符号会被收入标准中。所包含“字符”的集合就叫作“字符集”。好比:GB2312, GBK, JIS 等
规定每一个“字符”分别用一个字节仍是多个字节存储,用哪些字节来存储,这个规定就叫作“编码”。
各国因语言不一样,包含的字符也不一样, 那么在编码的时候各自的字符集也不尽相同,例如:汉字标准(GB2312)
注:“UNICODE 字符集”包含了各类语言中使用到的全部“字符”。用来给 UNICODE 字符集编码的标准有不少种,好比:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig、
4:经常使用的编码
简单介绍一下经常使用的编码规则,为后边的章节作一个准备。在这里,咱们根据编码规则的特色,把全部的编码分红三类:
分类 | 编码标准 | 说明 |
单字节字符编码 | ISO-8859-1 | 最简单的编码规则,每个字节直接做为一个 UNICODE 字符。好比,[0xD6, 0xD0] 这两个字节,经过 iso-8859-1 转化为字符串时,将直接获得 [0x00D6, 0x00D0] 两个 UNICODE 字符,即 "ÖÐ"。 反之,将 UNICODE 字符串经过 iso-8859-1 转化为字节串时,只能正常转化 0~255 范围的字符。 |
ANSI 编码 | GB2312, BIG5, Shift_JIS, ISO-8859-2 …… |
把 UNICODE 字符串经过 ANSI 编码转化为“字节串”时,根据各自编码的规定,一个 UNICODE 字符可能转化成一个字节或多个字节。 反之,将字节串转化成字符串时,也可能多个字节转化成一个字符。好比,[0xD6, 0xD0] 这两个字节,经过 GB2312 转化为字符串时,将获得 [0x4E2D] 一个字符,即 '中' 字。 “ANSI 编码”的特色: 1. 这些“ANSI 编码标准”都只能处理各自语言范围以内的 UNICODE 字符。 2. “UNICODE 字符”与“转换出来的字节”之间的关系是人为规定的。 |
UNICODE 编码 | UTF-8, UTF-16, UnicodeBig …… |
与“ANSI 编码”相似的,把字符串经过 UNICODE 编码转化成“字节串”时,一个 UNICODE 字符可能转化成一个字节或多个字节。 与“ANSI 编码”不一样的是: 1. 这些“UNICODE 编码”可以处理全部的 UNICODE 字符。 2. “UNICODE 字符”与“转换出来的字节”之间是能够经过计算获得的。 |
5:字符与编码在代码中的实现
在 C++ 和 Java 中,用来表明“字符”和“字节”的数据类型,以及进行编码的方法:
类型或操做 | C++ | Java |
字符 | wchar_t | char |
字节 | char | byte |
ANSI 字符串 | char[] | byte[] |
UNICODE 字符串 | wchar_t[] | String |
字节串→字符串 | mbstowcs(), MultiByteToWideChar() | string = new String(bytes, "encoding") |
字符串→字节串 | wcstombs(), WideCharToMultiByte() | bytes = string.getBytes("encoding") |
以上须要注意几点:
声明一段字符串常量:
// ANSI 字符串,内容长度 7 字节 char sz[20] = "中文123"; // UNICODE 字符串,内容长度 5 个 wchar_t(10 字节) wchar_t wsz[20] = L"\x4E2D\x6587\x0031\x0032\x0033"; |
UNICODE 字符串的 I/O 操做,字符与字节的转换操做:
// 运行时设定当前 ANSI 编码,VC 格式 setlocale(LC_ALL, ".936"); // GCC 中格式 setlocale(LC_ALL, "zh_CN.GBK"); // Visual C++ 中使用小写 %s,按照 setlocale 指定编码输出到文件 // GCC 中使用大写 %S fwprintf(fp, L"%s\n", wsz); // 把 UNICODE 字符串按照 setlocale 指定的编码转换成字节 wcstombs(sz, wsz, 20); // 把字节串按照 setlocale 指定的编码转换成 UNICODE 字符串 mbstowcs(wsz, sz, 20); |
在 Visual C++ 中,UNICODE 字符串常量有更简单的表示方法。若是源程序的编码与当前默认 ANSI 编码不符,则须要使用 #pragma setlocale,告诉编译器源程序使用的编码:
// 若是源程序的编码与当前默认 ANSI 编码不一致, // 则须要此行,编译时用来指明当前源程序使用的编码 #pragma setlocale(".936") // UNICODE 字符串常量,内容长度 10 字节 wchar_t wsz[20] = L"中文123"; |
以上须要注意 #pragma setlocale 与 setlocale(LC_ALL, "") 的做用是不一样的,#pragma setlocale 在编译时起做用,setlocale() 在运行时起做用。
对编码的误解 | |
误解一 | 在将“字节串”转化成“UNICODE 字符串”时,好比在读取文本文件时,或者经过网络传输文本时,容易将“字节串”简单地做为单字节字符串,采用每“一个字节”就是“一个字符”的方法进行转化。 而实际上,在非英文的环境中,应该将“字节串”做为 ANSI 字符串,采用适当的编码来获得 UNICODE 字符串,有可能“多个字节”才能获得“一个字符”。 一般,一直在英文环境下作开发的程序员们,容易有这种误解。 |
误解二 | 在 DOS,Windows 98 等非 UNICODE 环境下,字符串都是以 ANSI 编码的字节形式存在的。这种以字节形式存在的字符串,必须知道是哪一种编码才能被正确地使用。这使咱们造成了一个惯性思惟:“字符串的编码”。 当 UNICODE 被支持后,Java 中的 String 是以字符的“序号”来存储的,不是以“某种编码的字节”来存储的,所以已经不存在“字符串的编码”这个概念了。只有在“字符串”与“字节串”转化时,或 者,将一个“字节串”当成一个 ANSI 字符串时,才有编码的概念。 很多的人都有这个误解。 |
第一种误解,每每是致使乱码产生的缘由。第二种误解,每每致使原本容易纠正的乱码问题变得更复杂。
在这里,咱们能够看到,其中所讲的“误解一”,即采用每“一个字节”就是“一个字符”的转化方法,实际上也就等同于采用 iso-8859-1 进行转化。所以,咱们经常使用 bytes = string.getBytes("iso-8859-1") 来进行逆向操做,获得原始的“字节串”。而后再使用正确的 ANSI 编码,好比 string = new String(bytes, "GB2312"),来获得正确的“UNICODE 字符串”。
非 UNICODE 程序中的字符串,都是以某种 ANSI 编码形式存在的。若是程序运行时的语言环境与开发时的语言环境不一样,将会致使 ANSI 字符串的显示失败。
好比,在日文环境下开发的非 UNICODE 的日文程序界面,拿到中文环境下运行时,界面上将显示乱码。若是这个日文程序界面改成采用 UNICODE 来记录字符串,那么当在中文环境下运行时,界面上将能够显示正常的日文。
因为客观缘由,有时候咱们必须在中文操做系统下运行非 UNICODE 的日文软件,这时咱们能够采用一些工具,好比,南极星,AppLocale 等,暂时的模拟不一样的语言环境。