为了正常的体验网站,请在浏览器设置里面开启Javascript功能!
首页 > 字符字节与编码

字符字节与编码

2018-03-17 15页 doc 39KB 18阅读

用户头像

is_737352

暂无简介

举报
字符字节与编码字符字节与编码 理解编码的关键,是要把字符的概念和字节的概念理解准确。这两个概念容易混淆,我们在此做一下区分: '1', '中', 'a', '$', 字符 人们使用的记号,抽象意义上的一个符号。 '¥', …… 计算机中存储数据的单元,一个8位的二进制数,是一个很具体的存0x01, 0x45, 字节 储空间。 0xFA, …… 在内存中,如果“字符”是以 ANSI 形式存在的,一个字符可能使"中文123" ANSI 用一个字节或多个字节来表示,那么我们称这种字符串为 ANSI 字符串 (占7字节) 或者。 在内存中...
字符字节与编码
字符字节与编码 理解编码的关键,是要把字符的概念和字节的概念理解准确。这两个概念容易混淆,我们在此做一下区分: '1', '中', 'a', '$', 字符 人们使用的记号,抽象意义上的一个符号。 '¥', …… 计算机中存储数据的单元,一个8位的二进制数,是一个很具体的存0x01, 0x45, 字节 储空间。 0xFA, …… 在内存中,如果“字符”是以 ANSI 形式存在的,一个字符可能使"中文123" ANSI 用一个字节或多个字节来示,那么我们称这种字符串为 ANSI 字符串 (占7字节) 或者。 在内存中,如果“字符”是以在 UNICODE 中的序号存在的,那么我们L"中文123" UNICODE 字符串 称这种字符串为 UNICODE 或者。 (占10字节) 由于不同 ANSI 编码所规定的标准是不相同的,因此,对于一个给定的,我们必须知道它采用的是哪一种编码规则,才能够知道它包含了哪些“字符”。而对于 UNICODE 来说,不管在什么环境下,它所代表的“字符”总是不变的 各个国家和地区所制定的不同 ANSI 编码标准中,都只规定了各自语言所需的“字符”。比如:汉字标准 (GB2312)中没有规定韩国语字符怎样存储。这些 ANSI 编码标准所规定的内容包含两层含义: 1. 使用哪些字符。也就是说哪些汉字,字母和符号会被收入标准中。所包含“字符”的集合就叫做“ ”。 2. 规定每个“字符”分别用一个字节还是多个字节存储,用哪些字节来存储,这个规定就叫做“”。 各个国家和地区在制定编码标准的时候,“字符的集合”和“编码”一般都是同时制定的。因此,平常我们所 说的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”这层含义外,同时也包含了“编码” 的含义。 “UNICODE ”包含了各种语言中使用到的所有“字符”。用来给 UNICODE 字符集编码的标准有很 多种,比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等。 简单介绍一下常用的编码规则,为后边的章节做一个准备。在这里,我们根据编码规则的特点,把所有的 编码分成三类: 最简单的编码规则,每一个字节直接作为一个 UNICODE 字 符。比如,[0xD6, 0xD0] 这两个字节,通过 iso-8859-1 转 化为字符串时,将直接得到 [0x00D6, 0x00D0] 两个 单字节字符编码 UNICODE 字符,即 "ÖÐ"。 ISO-8859-1 反之,将 UNICODE 字符串通过 iso-8859-1 转化为字节 串时,只能正常转化 0~255 范围的字符。 把 UNICODE 字符串通过 ANSI 编码转化为“字节串”时, 根据各自编码的规定,一个 UNICODE 字符可能转化成一个 字节或多个字节。 反之,将字节串转化成字符串时,也可能多个字节转化成一GB2312, 个字符。比如,[0xD6, 0xD0] 这两个字节,通过 GB2312 BIG5, ANSI 编码 转化为字符串时,将得到 [0x4E2D] 一个字符,即 '中' 字。 Shift_JIS, ISO-8859-2 …… “ANSI 编码”的特点: 1. 这些“ANSI 编码标准”都只能处理各自语言范围之内的 UNICODE 字符。 2. “UNICODE 字符”与“转换出来的字节”之间的关系是人为 规定的。 与“ANSI 编码”类似的,把字符串通过 UNICODE 编码转化 成“字节串”时,一个 UNICODE 字符可能转化成一个字节或 多个字节。 UTF-8, UNICODE 编码 与“ANSI 编码”不同的是: UTF-16, 1. 这些“UNICODE 编码”能够处理所有的 UNICODE 字UnicodeBig …… 符。 2. “UNICODE 字符”与“转换出来的字节”之间是可以通过计 算得到的。 我们实际上没有必要去深究每一种编码具体把某一个字符编码成了哪几个字节,我们只需要知道“编码”的概念就是把“字符”转化成“字节”就可以了。对于“UNICODE 编码”,由于它们是可以通过计算得到的,因 此,在特殊的场合,我们可以去了解某一种“UNICODE 编码”是怎样的规则。 2.1 在 C++ 和 Java 中,用来代表“字符”和“字节”的数据类型,以及进行编码的方法: C++ Java 字符 wchar_t char 字节 char byte ANSI 字符串 char[] byte[] UNICODE 字符串 wchar_t[] String mbstowcs(), string = new String(bytes, 字节串?字符串 MultiByteToWideChar() "encoding") wcstombs(), 字符串?字节串 bytes = string.getBytes("encoding") WideCharToMultiByte() 以上需要注意几点: 1. Java 中的 char 代表一个“UNICODE 字符(宽字节字符)”,而 C++ 中的 char 代表一个字 节。 2. MultiByteToWideChar() 和 WideCharToMultiByte() 是 Windows API 函数。 3. 3.1 在将“字节串”转化成“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 字符串”。 回页首 3.2 UNICODE 非 UNICODE 程序中的字符串,都是以某种 ANSI 编码形式存在的。如果程序运行时的语言环境与开发 时的语言环境不同,将会导致 ANSI 字符串的显示失败。 比如,在日文环境下开发的非 UNICODE 的日文程序界面,拿到中文环境下运行时,界面上将显示乱码。 如果这个日文程序界面改为采用 UNICODE 来字符串,那么当在中文环境下运行时,界面上将可以 显示正常的日文。 由于客观原因,有时候我们必须在中文操作系统下运行非 UNICODE 的日文软件,这时我们可以采用一 些工具,比如,南极星,AppLocale 等,暂时的模拟不同的语言环境。 回页首 3.3 当页面中的表单提交字符串时,首先把字符串按照当前页面的编码,转化成字节串。然后再将每个字节转 化成 "%XX" 的格式提交到 Web 服务器。比如,一个编码为 GB2312 的页面,提交 "中" 这个字符串时,提交给服务器的内容为 "%D6%D0"。 在服务器端,Web 服务器把收到的 "%D6%D0" 转化成 [0xD6, 0xD0] 两个字节,然后再根据 GB2312 编码规则得到 "中" 字。 在 Tomcat 服务器中,request.getParameter() 得到乱码时,常常是因为前面提到的“误解一”造成的。默认情况下,当提交 "%D6%D0" 给 Tomcat 服务器时,request.getParameter() 将返回 [0x00D6, 0x00D0] 两个 UNICODE 字符,而不是返回一个 "中" 字符。因此,我们需要使用 bytes = string.getBytes("iso-8859-1") 得到原始的字节串,再用 string = new String(bytes, "GB2312") 重新得到正确的字符串 "中"。 回页首 3.4 通过数据库客户端(比如 ODBC 或 JDBC)从数据库服务器中读取字符串时,客户端需要从服务器获知 所使用的 ANSI 编码。当数据库服务器发送字节流给客户端时,客户端负责将字节流按照正确的编码转化 成 UNICODE 字符串。 如果从数据库读取字符串时得到乱码,而数据库中存放的数据又是正确的,那么往往还是因为前面提到的 “误解一”造成的。解决的办法还是通过 string = new String( string.getBytes("iso-8859-1"), "GB2312") 的方法,重新得到原始的字节串,再重新使用正确的编码转化成字符串。 回页首 3.5 当一段 Text 或者 HTML 通过电子邮件传送时,发送的内容首先通过一种指定的转化成“字节串”,然后再把“字节串”通过一种指定的(Content-Transfer-Encoding)进行转化得到另一串“字节串”。比如,打开一封电子邮件源代码,可以看到类似的内容: Content-Type: text/plain; charset="gb2312" Content-Transfer-Encoding: base64 sbG+qcrQuqO17cf4yee74bGjz9W7+b3wudzA7dbQ0MQNCg0KvPKzxqO6uqO17cnnsaPW0NDE DQoNCg== 最常用的 Content-Transfer-Encoding 有 Base64 和 Quoted-Printable 两种。在对二进制文件或者中文文本进行转化时,Base64 得到的“字节串”比 Quoted-Printable 更短。在对英文文本进行转化时,Quoted-Printable 得到的“字节串”比 Base64 更短。 邮件的标题,用了一种更简短的格式来标注“字符编码”和“传输编码”。比如,标题内容为 "中",则在邮件源代码中表示为: // 正确的标题格式 Subject: =?GB2312?B?1tA=?= 其中, 第一个“=?”与“?”中间的部分指定了字符编码,在这个例子中指定的是 GB2312。 “?”与“?”中间的“B”代表 Base64。如果是“Q”则代表 Quoted-Printable。 最后“?”与“?=”之间的部分,就是经过 GB2312 转化成字节串,再经过 Base64 转化后的标题 内容。 如果“传输编码”改为 Quoted-Printable,同样,如果标题内容为 "中": // 正确的标题格式 Subject: =?GB2312?Q?=D6=D0?= 如果阅读邮件时出现乱码,一般是因为“字符编码”或“传输编码”指定有误,或者是没有指定。比如,有的 发邮件组件在发送邮件时,标题 "中": // 错误的标题格式 Subject: =?ISO-8859-1?Q?=D6=D0?= 这样的表示,实际上是明确指明了标题为 [0x00D6, 0x00D0],即 "ÖÐ",而不是 "中"。 这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念,增程序员趣味读物:谈谈Unicode编码 进知识,类似于打RPG游戏的升级。整理这篇文章的动机是两个问题: 使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种 编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢? 我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节, 分别是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。但这些标记是 基于什么标准呢? 最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转 换。对于Unicode(UCS2)、GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想 不起来UTF-16和UCS2有什么关系。 查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些Unicode的细节。写成一篇文章,送 给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂,但要求读者知道什么是字节,什么是十六进制。 0big endianlittle endian big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。 还是将49写在前面,就是little endian。 “endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。 我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。 1 字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使 用7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。 GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码 范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。 GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区 和图形符号区。汉字区包括21003个字符。2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。现在的PC平台 必须支持GB18030,对嵌入式产品暂不作要求。所以手机、MP3一般只支持GB2312。 从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些中 总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文 编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK到GB18030都属于双字节字 符集 (DBCS)。 有的中文Windows的缺省内码还是GBK,可以通过GB18030升级包升级到GB18030。不过GB18030相对GBK增加的字符,普通人是很难用到的,通常我们还是用GBK指代中文Windows内码。 这里还有一些细节: GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。 在DBCS中,GB内码的存储格式始终是big endian,即高位在前。 GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而不用管低字节的高位是什么。 2UnicodeUCSUTF 前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49, 而GB码是BABA。 Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方 案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以 看作是"Unicode Character Set"的缩写。 根据维基百科全()的记载:历史上存在两个试图独立设计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。 UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码,是由UTF(UCS Transformation Format)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16。 IETF的RFC2781和RFC3629以RFC的一贯风格,清晰、明快又不失严谨地描述了UTF-16和UTF-8的编码方法。我总是记不得IETF是Internet Engineering Task Force的缩写。但IETF负责维 护的RFC是Internet上一切规范的基础。 3UCS-2UCS-4BMP UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个 字节(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏: UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。 UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells 只是最后一个字节不同,其余都相同。 group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为 0的码位被称作BMP。 将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节, 就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。 4UTF UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下: UCS-2编码(16进制) UTF-8 字节流(二进制) 0000 - 007F 0xxxxxxx 0080 - 07FF 110xxxxx 10xxxxxx 0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx 例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特 流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。 读者可以用记事本测试一下我们的编码是否正确。 UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0x10000的UCS码,定义了一个算法。不过由于实际使用的 UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。 5UTFBOM UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E,“乙”的 Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”? Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是 Byte Order Mark。BOM是一个有点小聪明的想法: 在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输 字符"ZERO WIDTH NO-BREAK SPACE"。 这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节 流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。 UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所 以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。 Windows就是使用BOM来标记文本文件的编码方式的。 6 本文主要参考的资料是 "Short overview of ISO-IEC 10646 and Unicode" ()。 我还找了两篇看上去不错的资料,不过因为我开始的疑问都找到了答案,所以就没有看: "Understanding Unicode A general introduction to the Unicode Standard" (;item_id=IWS-Chapter04a) "Character set encoding basics Understanding character set encodings and legacy encodings" (;item_id=IWS-Chapter03) 我写过UTF-8、UCS-2、GBK相互转换的软件包,包括使用Windows API和不使用Windows API的版本。以后有时间的话,我会整理一下放到我的个人主页上()。 我是想清楚所有问题后才开始写这篇文章的,原以为一会儿就能写好。没想到考虑措辞和查证细节花 费了很长时间,竟然从下午1:30写到9:00。希望有读者能从中受益。
/
本文档为【字符字节与编码】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索