字节编码方式,字节 编码
字符、字节和编码
【原创文章,转载请保留或注明出处:http://www.regexlab.com/zh/encoding.htm】
级别:中级
本文介绍了文字和代码的发展过程,以及对相关概念的正确理解。举例说明了编码在一些实际应用中的实现方法。然后,本文讲述了一些常见的对文字和代码的误解,这些误解导致乱码的原因,以及消除乱码的方法。本文涵盖了“中文问题”和“乱码问题”。
掌握编码问题的关键是正确理解相关概念,编码涉及的技术其实很简单。所以,读这篇文章的时候,需要慢慢读,多思考。
介绍
“字符符号与编码”是一个经常讨论的话题。即便如此,频繁出现的乱码依然困扰着大家。虽然我们有很多消除乱码的方法,但不一定了解这些方法的内在原理。有些乱码其实是底层代码本身的问题造成的。所以,不仅新手对字符编码很迷茫,一些底层开发人员也缺乏对字符编码的准确理解。
美国国家标准协会
(本地化)
为了使计算机支持更多的语言,通常用0x80~0xFF范围内的2个字节来表示1个字符。例如,在中文操作系统中,汉字“中”存储在[0xD6,0xD0]中。
不同的国家和地区制定了不同的标准,产生了GB2312、BIG5、JIS等各自的编码标准。这两个字节用来表示汉字的各种扩展编码方式,称为ANSI编码。在简体中文系统下,ANSI码代表GB2312码,而在日文操作系统下,ANSI码代表JIS码。
不同的ANSI代码互不兼容。在国际间交换信息时,不可能在同一个ANSI编码文本中存储属于两种语言的单词。
中文DOS,中文视窗95/98,日文视窗95/98
采用双字节对字符进行编码
(国际化)
为了使国际信息交换更加方便,国际组织制定了UNICODE字符集,为各种语言中的每个字符设置一个统一的唯一编号,以满足跨语言、跨平台的文本转换和处理的要求。
Windows NT/2000/XP、Linux、Java
字符串在内存中的存储方法:
在ASCII中,单字节字符串使用一个字节存储一个字符(SBCS)。例如,内存中的“Bob123”是:
在使用ANSI编码支持多种语言的阶段,每个字符由一个或多个字节(MBCS)表示,因此以这种方式存储的字符也称为多字节字符。例如,“中文123”在中文Windows 95内存中有7个字节,每个中文字符有2个字节,每个英文和数字字符有1个字节:
采用UNICODE后,计算机在存储字符串时,将存储UNICODE字符集中每个字符的序列号。目前计算机一般用2个字节(16位)来存储一个序列号(DBCS),所以这样存储的字符也叫宽字节字符。例如,在Windows 2000下,字符串“中文123”实际上在内存中存储了五个序列号:
1.2字符、字节和字符串
编码的关键是准确理解字符和字节的概念。这两个概念很容易混淆,所以我们在这里做个区分:
计算机中存储数据的单位,8位二进制数,是一个非常特定的存储空间。
0x01、0x45、0xFA、……
美国国家标准学会
字符串
在内存中,如果“字符”以ANSI编码形式存在,一个字符可能用一个字节或多个字节来表示,那么我们把这个字符串称为ANSI字符串或多字节字符串。
中文123
(7字节)
采用双字节对字符进行编码
字符串
在内存中,如果“字符”以UNICODE中的序列号存在,那么我们称之为UNICODE字符串或宽字节字符串。
l 中文123
(10字节)
因为不同ANSI码规定的标准不一样,对于给定的多字节字符串,我们必须知道它采用的是哪种编码规则,才能知道它包含哪些“字符”。至于UNICODE字符串,无论在什么环境下,它所代表的“字符”的内容总是相同的。
1.3字符集和编码
在各个国家和地区制定的不同ANSI编码标准中,只规定了各自语言所要求的“字符”。例如,汉字标准(GB2312)没有规定如何存储韩文字符。这些ANSI编码标准包含两层含义:
要使用哪些字符。也就是说,标准中会包括哪些汉字、字母、符号。包含“字符”的集合称为“字符集”。
规定了每个“字符”是用一个字节存储还是多个字节存储,用哪些字节存储。这个规则叫做“编码”。
国家和地区在制定编码标准时,一般会同时制定“字符集”和“编码”。因此,我们通常所说的“字符集”,如GB2312、GBK、JIS等。不仅有“字符集合”的意思,还包括“编码”的意思。
UNICODE字符集包含各种语言中使用的所有字符。编码UNICODE字符集的标准有很多,比如UTF-8、UTF-7、UTF-16、Unicode Little、Unicode Big等。
1.4常用代码介绍
简单介绍一下常用的编码规则,为后面的章节做一个准备。这里,根据编码规则的特点,我们将所有的编码分为三类:
ISO-8859-1
最简单的编码规则是每个字节直接用作一个UNICODE字符。例如,当iso-8859-1将[0xD6,0xD0]转换成字符串时,会直接得到两个UNICODE字符[0x0d 6,0x0d 0],即 。
反之,当UNICODE字符串被iso-8859-1转换为字节字符串时,只能正常转换0~255范围内的字符。
GB2312,
BIG5,
移位_JIS,
ISO-8859-2 ……
当通过ANSI编码将UNICODE字符串转换为“字节字符串”时,UNICODE字符可以根据各自的编码规则转换为一个或多个字节。
相反,在将一串字节转换成一个字符串时,也可以将多个字节转换成一个字符。例如,当GB2312将[0xD6,0xD0]转换成字符串时,会得到一个字符[0x4E2D],即中间字符。
“ANSI编码”的特征:
1.这些“ANSI编码标准”只能处理各自语言范围内的UNICODE字符。
2.“Unicode字符”和“转换后的字节”之间的关系是人为规定的。
UTF八号,
UTF-16,UnicodeBig ……
与ANSI编码类似,当字符串通过UNICODE编码转换为“字节字符串”时,UNICODE字符可能会转换为一个字节或多个字节。
与“ANSI编码”不同:
1.这些“UNICODE编码”可以处理所有的UNICODE字符。
2.可以计算出“Unicode字符”和“转换后的字节”之间的关系。
实际上,我们不需要去探究某个字符被每个代码编码到哪些字节中。我们只需要知道“编码”的概念就是把“字符”转换成“字节”。对于“UNICODE编码”,因为可以计算,所以可以在特殊场合了解某个“UNICODE编码”的规则。
2.程序中字符和代码的实现
2.1程序中的字符和字节
在C和Java中,用于表示“字符”和“字节”的数据类型,以及编码方法:
Java中的Char代表一个UNICODE字符(宽字节字符),而C中的char代表一个字节。
MultiByteToWideChar()和WideCharToMultiByte()是Windows API函数。
//ANSI字符串,内容长度为7个字节
Char [20]=中文123 ;
//内容长度为5 wchar_t(10字节)的UNICODE字符串
wchar _ t wsz[20]=L /x4E2D/x 6587/x 0031/x 0032/x 0033 ;
//在运行时设置当前的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);
//根据setlocale指定的编码将UNICODE字符串转换为字节
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()在运行时工作。
//Java代码,直接用中文写的
String=中文123 ;
//获取长度5,因为它是5个字符
system . out . println(string . length());
字符串输入输出操作,字符和字节转换操作。在Java包java.io.*中,以“Stream”结尾的类一般用来操作“字节串”,以“Reader”和“Writer”结尾的类一般用来操作“串”。
//将字符串转换为字节字符串。
//根据GB2312获取字节(获取多字节字符串)
byte[]bytes=string . getbytes( GB 2312 );
//根据GB2312从字节获取UNICODE字符串
string=新字符串(字节, GB 2312 );
//要将字符串按照一定的编码写入文本文件,有两种方法:
//第一种方法:用Stream类写出按照指定编码转换后的字节串。
output stream OS=new file output stream( 1 . txt );
os.write(字节);
OS . close();
//第二种方法:用指定的代码构造Writer来写字符串。
writer ow=new output streamwriter(new file output stream( 2 . txt ), GB 2312 );
ow.write(字符串);
ow . close();
/*最后的1.txt和2.txt是7个字节*/
如果java的源代码与当前默认的ANSI代码不匹配,需要在编译时注明源代码。例如:
以上,要注意区分源程序的编码和I/O操作的编码。前者在编译时工作,后者在运行时工作。
在将“字节串”转换为“UNICODE串”时,例如读取文本文件或通过网络传输文本时,很容易将“字节串”简单地视为单字节串,采用每“字节”为“一个字符”的方法进行转换。
实际上,在非英语环境中,“字节串”应该被用作ANSI字符串,并且应该采用适当的编码来获得UNICODE字符串。有可能“多个字节”可以得到“一个字符”。
通常一直用英文开发的程序员容易产生这种误解。
在DOS、Windows 98和其他非UNICODE环境中,字符串以ANSI编码的字节形式存在。这个字符串是以字节的形式存在的,需要知道正确使用的是哪种编码。这让我们形成了一种惯性思维:“字符串编码”。
当支持UNICODE后,Java中的字符串存储为字符的“序列号”,而不是“一些编码的字节”,所以“字符串的编码”这个概念就不复存在了。只有当“字符串”和“字节串”进行转换,或者将一个“字节串”视为ANSI字符串时,才存在编码的概念。
很多人都有这种误解。
第一种误解往往是乱码的原因。第二种误解往往会让本来容易纠正的乱码问题变得更加复杂。
"?"还有“?”中间的“B”代表Base64。如果是“Q”,表示引用-可打印。
最后“?”用“?="之间的部分是由GB2312转换成字节串,再由Base64转换的标题内容。
如果“传输代码”更改为“报价-可打印”,同样,如果标题内容为“中等”:
如果阅读时出现乱码,通常是因为“字符编码”或“传输编码”指定不正确或不正确。例如,一些电子邮件组件发送标题为“medium”的电子邮件:
4.纠正几个误解
误解:“ISO-8859-1是国际代码?”
不会。Iso-8859-1只是单字节字符集中最简单的一种,即“字节数”与“UNICODE字符数”一致的编码规则。当我们在不知道是哪种ANSI编码的情况下,想把一个“字节串”转换成“字符串”时,应该先把“每一个字节”临时转换成“一个字符”,这样不会造成信息丢失。然后,可以使用bytes=string . getbytes( ISO-8859-1 )的方法来恢复原始的字节字符串。
误解:“Java里,怎么知道一个字符串的内部代码?”
在Java中,字符串类java.lang.String处理UNICODE字符串,而不是ANSI字符串。我们只需要把字符串当作“抽象符号的字符串”。所以不存在字符串内码的问题。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。