<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
><channel><title>简单生活 —— Kevin Yang的博客 &#187; 字符编解码</title> <atom:link href="http://www.imkevinyang.com/tags/%e5%ad%97%e7%ac%a6%e7%bc%96%e8%a7%a3%e7%a0%81/feed" rel="self" type="application/rss+xml" /><link>http://www.imkevinyang.com</link> <description>It&#039;s all about sharing</description> <lastBuildDate>Mon, 06 Sep 2010 08:00:00 +0000</lastBuildDate> <generator>http://wordpress.org/?v=2.9.1</generator> <language>en</language> <sy:updatePeriod>hourly</sy:updatePeriod> <sy:updateFrequency>1</sy:updateFrequency> <item><title>关于字符编码，你所需要知道的</title><link>http://www.imkevinyang.com/2010/06/%e5%85%b3%e4%ba%8e%e5%ad%97%e7%ac%a6%e7%bc%96%e7%a0%81%ef%bc%8c%e4%bd%a0%e6%89%80%e9%9c%80%e8%a6%81%e7%9f%a5%e9%81%93%e7%9a%84.html</link> <comments>http://www.imkevinyang.com/2010/06/%e5%85%b3%e4%ba%8e%e5%ad%97%e7%ac%a6%e7%bc%96%e7%a0%81%ef%bc%8c%e4%bd%a0%e6%89%80%e9%9c%80%e8%a6%81%e7%9f%a5%e9%81%93%e7%9a%84.html#comments</comments> <pubDate>Fri, 18 Jun 2010 13:41:05 +0000</pubDate> <dc:creator>Kevin Yang</dc:creator> <category><![CDATA[其他随笔]]></category> <category><![CDATA[ASCII]]></category> <category><![CDATA[GB18030]]></category> <category><![CDATA[GB2312]]></category> <category><![CDATA[GBK]]></category> <category><![CDATA[OEM]]></category> <category><![CDATA[Unicode]]></category> <category><![CDATA[UTF-16]]></category> <category><![CDATA[UTF-8]]></category> <category><![CDATA[字符]]></category> <category><![CDATA[字符编解码]]></category> <category><![CDATA[编码]]></category><guid isPermaLink="false">http://www.imkevinyang.com/2010/06/%e5%85%b3%e4%ba%8e%e5%ad%97%e7%ac%a6%e7%bc%96%e7%a0%81%ef%bc%8c%e4%bd%a0%e6%89%80%e9%9c%80%e8%a6%81%e7%9f%a5%e9%81%93%e7%9a%84.html</guid> <description><![CDATA[<p>字符编码的问题看似很小，经常被技术人员忽视，但是很容易导致一些莫名其妙的问题。这里总结了一下字符编码的一些普及性的知识，希望对大家有所帮助。</p><h2>还是得从ASCII码说起</h2><p>说到字符编码，不得不说ASCII码的简史。计算机一开始发明的时候是用来解决数字计算的问题，后来人们发现，计算机还可以做更多的事，例如文本处理。但由于计算机只识“数”，因此人们必须告诉计算机哪个数字来代表哪个特定字符，例如65代表字母‘A’，66代表字母‘B’，以此类推。但是<strong>计&#8230;</strong></p>]]></description> <content:encoded><![CDATA[<p>字符编码的问题看似很小，经常被技术人员忽视，但是很容易导致一些莫名其妙的问题。这里总结了一下字符编码的一些普及性的知识，希望对大家有所帮助。</p><h2>还是得从ASCII码说起</h2></p><p>说到字符编码，不得不说ASCII码的简史。计算机一开始发明的时候是用来解决数字计算的问题，后来人们发现，计算机还可以做更多的事，例如文本处理。但由于计算机只识“数”，因此人们必须告诉计算机哪个数字来代表哪个特定字符，例如65代表字母‘A’，66代表字母‘B’，以此类推。但是<strong>计算机之间字符-数字的对应关系必须得一致，否则就会造成同一段数字在不同计算机上显示出来的字符不一样</strong>。因此美国国家标准协会ANSI制定了一个标准，规定了常用字符的集合以及每个字符对应的编号，这就是ASCII字符集（Character Set），也称ASCII码。</p><p>当时的计算机普遍使用8比特字节作为最小的存储和处理单元，加之当时用到的字符也很少，26个大小写英文字母还有数字再加上其他常用符号，也不到100个，因此使用7个比特位就可以高效的存储和处理ASCII码，剩下最高位1比特被用作一些通讯系统的奇偶校验。</p><blockquote><p>注意，字节代表系统能够处理的最小单位，不一定是8比特。只是现代计算机的事实标准就是用8比特来代表一个字节。在很多技术规格文献中，为了避免产生歧义，更倾向于使用8位组（Octet）而不是字节（Byte）这个术语来强调8个比特的二进制流。下文中为了便于理解，我会延用大家熟悉的“字节”这个概念。</p></blockquote><p><img style="display: inline; margin-left: 0px; margin-right: 0px" alt="ASCII table" src="http://www.joelonsoftware.com/pictures/unicode/ascii.png" width="274" height="146" /></p><p>ASCII字符集由95个可打印字符（0x20-0x7E）和33个控制字符（0x00-0x19，0x7F）组成。可打印字符用于显示在输出设备上，例如荧屏或者打印纸上，控制字符用于向计算机发出一些特殊指令，例如0x07会让计算机发出哔的一声，0x00通常用于指示字符串的结束，0x0D和0x0A用于指示打印机的打印针头退到行首（回车）并移到下一行（换行）。</p><p>那时候的字符编解码系统非常简单，就是简单的查表过程。例如将字符序列编码为二进制流写入存储设备，只需要在ASCII字符集中依次找到字符对应的字节，然后直接将该字节写入存储设备即可。解码二进制流的过程也是类似。</p><h2>OEM字符集的衍生</h2><p>当计算机开始发展起来的时候，人们逐渐发现，ASCII字符集里那可怜的128个字符已经不能再满足他们的需求了。人们就在想，一个字节能够表示的数字（编号）有256个，而ASCII字符只用到了0x00~0x7F，也就是占用了前128个，后面128个数字不用白不用，因此很多人打起了后面这128个数字的主意。可是问题在于，很多人同时有这样的想法，但是大家对于0x80-0xFF这后面的128个数字分别对应什么样的字符，却有各自的想法。这就导致了当时销往世界各地的机器上出现了大量各式各样的OEM字符集。</p><p>下面这张表是IBM-PC机推出的其中一个OEM字符集，字符集的前128个字符和ASCII字符集的基本一致（为什么说基本一致呢，是因为前32个控制字符在某些情况下会被IBM-PC机当作可打印字符解释），后面128个字符空间加入了一些欧洲国家用到的重音字符，以及一些用于画线条画的字符。</p><p><img style="display: inline" title="IBM-PC OEM字符集" alt="IBM-PC OEM字符集" src="http://www.joelonsoftware.com/pictures/unicode/oem.png" width="271" height="209" /></p><p>事实上，大部分OEM字符集是兼容ASCII字符集的，也就是说，大家对于0x00~0x7F这个范围的解释基本是相同的，而对于后半部分0x80~0xFF的解释却不一定相同。甚至有时候同样的字符在不同OEM字符集中对应的字节也是不同的。</p><p>不同的OEM字符集导致人们无法跨机器交流各种文档。例如职员甲发了一封简历résumés给职员乙，结果职员乙看到的却是r<img alt="?" src="http://www.joelonsoftware.com/pictures/unicode/gimel.png" width="5" height="9" />sum<img alt="?" src="http://www.joelonsoftware.com/pictures/unicode/gimel.png" width="5" height="9" />s，因为é字符在职员甲机器上的OEM字符集中对应的字节是0x82，而在职员乙的机器上，由于使用的OEM字符集不同，对0x82字节解码后得到的字符却是<img alt="?" src="http://www.joelonsoftware.com/pictures/unicode/gimel.png" width="5" height="9" />。</p><h2>多字节字符集（MBCS）和中文字符集</h2><p>上面我们提到的字符集都是基于单字节编码，也就是说，一个字节翻译成一个字符。这对于拉丁语系国家来说可能没有什么问题，因为他们通过扩展第8个比特，就可以得到256个字符了，足够用了。但是对于亚洲国家来说，256个字符是远远不够用的。因此这些国家的人为了用上电脑，又要保持和ASCII字符集的兼容，就发明了多字节编码方式，相应的字符集就称为多字节字符集。例如中国使用的就是双字节字符集编码（DBCS，Double Byte Character Set）。</p><p>对于单字节字符集来说，代码页中只需要有一张码表即可，上面记录着256个数字代表的字符。程序只需要做简单的查表操作就可以完成编解码的过程。</p><blockquote><p>代码页是字符集编码的具体实现，你可以把他理解为一张“字符-字节”映射表，通过查表实现“字符-字节”的翻译。下面会有更详细的描述。</p></blockquote><p>而对于多字节字符集，代码页中通常会有很多码表。那么程序怎么知道该使用哪张码表去解码二进制流呢？答案是，<strong>根据第一个字节来选择不同的码表进行解析</strong>。</p><p>例如目前最常用的中文字符集GB2312，涵盖了所有简体字符以及一部分其他字符；GBK（K代表扩展的意思）则在GB2312的基础上加入了对繁体字符等其他非简体字符（GB18030字符集不是双字节字符集，我们在讲Unicode的时候会提到）。这两个字符集的字符都是使用1-2个字节来表示。Windows系统采用936代码页来实现对GBK字符集的编解码。在解析字节流的时候，如果遇到字节的最高位是0的话，那么就使用936代码页中的第1张码表进行解码，这就和单字节字符集的编解码方式一致了。</p><p><img style="display: inline" title="image" alt="image" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image15.png" width="416" height="305" /></p><p>当字节的高位是1的时候，确切的说，当第一个字节位于0x<code>81</code>–0x<code>FE之间时，根据第一个字节不同找到代码页中的相应的码表，例如当第一个字节是0x81，那么对应936中的下面这张码表：</code></p><p><img style="display: inline" title="image" alt="image" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image9.png" width="442" height="374" /></p><p>（关于936代码页中完整的码表信息，参见MSDN：<a title="http://msdn.microsoft.com/en-us/library/cc194913%28v=MSDN.10%29.aspx" href="http://msdn.microsoft.com/en-us/library/cc194913%28v=MSDN.10%29.aspx">http://msdn.microsoft.com/en-us/library/cc194913%28v=MSDN.10%29.aspx</a>.）</p><p>按照936代码页的码表，当程序遇到连续字节流0x81 0x40的时候，就会解码为“丂”字符。</p><h2>ANSI标准、国家标准、ISO标准</h2><p>不同ASCII衍生字符集的出现，让文档交流变得非常困难，因此各种组织都陆续进行了标准化流程。例如美国ANSI组织制定了ANSI标准字符编码（注意，<strong>我们现在通常说到ANSI编码，通常指的是平台的默认编码，例如英文操作系统中是ISO-8859-1，中文系统是GBK</strong>），ISO组织制定的各种ISO标准字符编码，还有各国也会制定一些国家标准字符集，例如中国的GBK，GB2312和GB18030。</p><p>操作系统在发布的时候，通常会往机器里预装这些标准的字符集还有平台专用的字符集，这样只要你的文档是使用标准字符集编写的，通用性就比较高了。例如你用GB2312字符集编写的文档，在中国大陆内的任何机器上都能正确显示。同时，我们也可以在一台机器上阅读多个国家不同语言的文档了，前提是本机必须安装该文档使用的字符集。</p><h2>Unicode的出现</h2><p>虽然通过使用不同字符集，我们可以在一台机器上查阅不同语言的文档，但是我们仍然无法解决一个问题：<strong>在一份文档中显示所有字符</strong>。为了解决这个问题，我们需要一个全人类达成共识的巨大的字符集，这就是Unicode字符集。</p><h3>Unicode字符集概述</h3><p>Unicode字符集涵盖了目前人类使用的所有字符，并为每个字符进行统一编号，分配唯一的字符码（Code Point）。Unicode字符集将所有字符按照使用上的频繁度划分为17个层面（Plane），每个层面上有2<sup>16</sup>=65536个字符码空间。</p><p><img style="display: inline" title="image" alt="image" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image29.png" width="556" height="224" /></p><p>其中第0个层面BMP，基本涵盖了当今世界用到的所有字符。其他的层面要么是用来表示一些远古时期的文字，要么是留作扩展。我们平常用到的Unicode字符，一般都是位于BMP层面上的。目前Unicode字符集中尚有大量字符空间未使用。</p><h3>编码系统的变化</h3><p>在Unicode出现之前，所有的字符集都是和具体编码方案绑定在一起的，都是直接将字符和最终字节流绑定死了，例如ASCII编码系统规定使用7比特来编码ASCII字符集；GB2312以及GBK字符集，限定了使用最多2个字节来编码所有字符，并且规定了字节序。这样的编码系统通常用简单的查表，也就是通过代码页就可以直接将字符映射为存储设备上的字节流了。例如下面这个例子：</p><p><img style="display: inline" title="image" alt="image" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image17.png" width="373" height="275" /></p><p>这种方式的缺点在于，字符和字节流之间耦合得太紧密了，从而限定了字符集的扩展能力。假设以后火星人入住地球了，要往现有字符集中加入火星文就变得很难甚至不可能了，而且很容易破坏现有的编码规则。</p><p>因此Unicode在设计上考虑到了这一点，将字符集和字符编码方案分离开。</p><p><img style="display: inline" title="字符编码系统" alt="字符编码系统" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image10.png" width="329" height="265" /></p><p>也就是说，<strong>虽然每个字符在Unicode字符集中都能找到唯一确定的编号（字符码，又称Unicode码），但是决定最终字节流的却是具体的字符编码</strong>。例如同样是对Unicode字符“A”进行编码，UTF-8字符编码得到的字节流是0x41，而UTF-16（大端模式）得到的是0x00 0x41。</p><h3>常见的Unicode编码</h3><p><strong>UCS-2/UTF-16</strong></p><p>如果要我们来实现Unicode字符集中BMP字符的编码方案，我们会怎么实现？由于BMP层面上有2<sup>16</sup>=65536个字符码，因此我们只需要两个字节就可以完全表示这所有的字符了。</p><p>举个例子，“中”的Unicode字符码是0x4E2D(01001110 00101101)，那么我们可以编码为01001110 00101101（大端）或者00101101 01001110 （小端）。</p><p>UCS-2和UTF-16对于BMP层面的字符均是使用2个字节来表示，并且编码得到的结果完全一致。不同之处在于，<strong>UCS-2最初设计的时候只考虑到BMP字符，因此使用固定2个字节长度，也就是说，他无法表示Unicode其他层面上的字符，而UTF-16为了解除这个限制，支持Unicode全字符集的编解码，采用了变长编码，最少使用2个字节，如果要编码BMP以外的字符，则需要4个字节结对</strong>，这里就不讨论那么远，有兴趣可以参考维基百科：<a title="UTF-16/UCS-2" href="http://en.wikipedia.org/wiki/UTF-16/UCS-2" target="_blank">UTF-16/UCS-2</a>。</p><p>Windows从NT时代开始就采用了UTF-16编码，很多流行的编程平台，例如.Net，Java，Qt还有Mac下的Cocoa等都是使用UTF-16作为基础的字符编码。例如代码中的字符串，在内存中相应的字节流就是用UTF-16编码过的。</p><p><strong>UTF-8</strong></p><p>UTF-8应该是目前应用最广泛的一种Unicode编码方案。由于UCS-2/UTF-16对于ASCII字符使用两个字节进行编码，存储和处理效率相对低下，并且由于ASCII字符经过UTF-16编码后得到的两个字节，高字节始终是0x00，很多C语言的函数都将此字节视为字符串末尾从而导致无法正确解析文本。因此一开始推出的时候遭到很多西方国家的抵触，大大影响了Unicode的推行。后来聪明的人们发明了UTF-8编码，解决了这个问题。</p><p>UTF-8编码方案采用1-4个字节来编码字符，方法其实也非常简单。</p><p><img style="display: inline" title="image" alt="image" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image11.png" width="492" height="345" /></p><p>（上图中的x代表Unicode码的低8位，y代表高8位）</p><p><strong>对于ASCII字符的编码使用单字节，和ASCII编码一摸一样，这样所有原先使用ASCII编解码的文档就可以直接转到UTF-8编码了。对于其他字符，则使用2-4个字节来表示，其中，首字节前置1的数目代表正确解析所需要的字节数，剩余字节的高2位始终是10。例如首字节是1110yyyy，前置有3个1，说明正确解析总共需要3个字节，需要和后面2个以10开头的字节结合才能正确解析得到字符</strong>。</p><p>关于UTF-8的更多信息，参考维基百科：<a title="UTF-8" href="http://en.wikipedia.org/wiki/UTF-8" target="_blank">UTF-8</a>。</p><p><strong>GB18030</strong></p><p>任何能够将Unicode字符映射为字节流的编码都属于Unicode编码。中国的GB18030编码，覆盖了Unicode所有的字符，因此也算是一种Unicode编码。只不过他的编码方式并不像UTF-8或者UTF-16一样，将Unicode字符的编号通过一定的规则进行转换，而只能通过查表的手段进行编码。</p><p>关于GB18030的更多信息，参考：<a title="GB18030" href="http://en.wikipedia.org/wiki/GB18030" target="_blank">GB18030</a>。</p><h3>Unicode相关的常见问题</h3><p><strong>Unicode是两个字节吗？</strong></p><p>Unicode只是定义了一个庞大的、全球通用的字符集，并为每个字符规定了唯一确定的编号，具体存储为什么样的字节流，取决于字符编码方案。推荐的Unicode编码是UTF-16和UTF-8。</p><p><strong>带签名的UTF-8指的是什么意思？</strong></p><p>带签名指的是字节流以BOM标记开始。很多软件会“智能”的探测当前字节流使用的字符编码，这种探测过程出于效率考虑，通常会提取字节流前面若干个字节，看看是否符合某些常见字符编码的编码规则。由于UTF-8和ASCII编码对于纯英文的编码是一样的，无法区分开来，因此通过在字节流最前面添加BOM标记可以告诉软件，当前使用的是Unicode编码，判别成功率就十分准确了。但是需要注意，不是所有软件或者程序都能正确处理BOM标记，例如PHP就不会检测BOM标记，直接把它当普通字节流解析了。因此如果你的PHP文件是采用带BOM标记的UTF-8进行编码的，那么有可能会出现问题。</p><p><strong>Unicode编码和以前的字符集编码有什么区别？</strong></p><p>早期字符编码、字符集和代码页等概念都是表达同一个意思。例如GB2312字符集、GB2312编码，936代码页，实际上说的是同个东西。但是对于Unicode则不同，Unicode字符集只是定义了字符的集合和唯一编号，Unicode编码，则是对UTF-8、UCS-2/UTF-16等具体编码方案的统称而已，并不是具体的编码方案。所以当需要用到字符编码的时候，你可以写gb2312，codepage936，utf-8，utf-16，但请不要写unicode（看过别人在网页的meta标签里头写charset=unicode，有感而发）。</p></p><h2>乱码问题</h2><p>乱码指的是程序显示出来的字符文本无法用任何语言去解读。一般情况下会包含大量?或者?。乱码问题是所有计算机用户或多或少会遇到的问题。<strong>造成乱码的原因就是因为使用了错误的字符编码去解码字节流</strong>，<strong>因此当我们在思考任何跟文本显示有关的问题时，请时刻保持清醒：当前使用的字符编码是什么</strong>。只有这样，我们才能正确分析和处理乱码问题。</p><p>例如最常见的网页乱码问题。如果你是网站技术人员，遇到这样的问题，需要检查以下原因：</p><ul><li>服务器返回的响应头Content-Type没有指明字符编码</li><li>网页内是否使用META HTTP-EQUIV标签指定了字符编码</li><li>网页文件本身存储时使用的字符编码和网页声明的字符编码是否一致</li></ul><p><img style="display: inline" title="image" alt="image" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image12.png" width="209" height="121" /> <img style="display: inline" title="image" alt="image" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image51.png" width="369" height="66" /></p><p>注意，网页解析的过程如果使用的字符编码不正确，还可能会导致脚本或者样式表出错。具体细节可以参考我以前写过的文章：<a href="http://www.imkevinyang.com/2009/08/%E6%96%87%E6%A1%A3%E5%AD%97%E7%AC%A6%E9%9B%86%E5%AF%BC%E8%87%B4%E7%9A%84%E8%84%9A%E6%9C%AC%E9%94%99%E8%AF%AF.html">文档字符集导致的脚本错误</a>和<a href="http://www.imkevinyang.com/2009/11/asp-net%E9%A1%B5%E9%9D%A2%E7%9A%84%E7%BC%96%E7%A0%81%E9%97%AE%E9%A2%98.html">Asp.Net页面的编码问题</a>。</p><p>不久前看到某技术论坛有人反馈，WinForm程序使用Clipboard类的GetData方法去访问剪切板中的HTML内容时会出现乱码的问题，我估计也是由于WinForm在获取HTML文本的时候没有用对正确的字符编码导致的。Windows剪贴板只支持UTF-8编码，也就是说你传入的文本都会被UTF-8编解码。这样一来，只要两个程序都是调用Windows剪切板API编程的话，那么复制粘贴的过程中不会出现乱码。除非一方在获取到剪贴板数据之后使用了错误的字符编码进行解码，才会得到乱码（我做了简单的WinForm剪切板编程实验，发现GetData使用的是系统默认编码，而不是UTF-8编码）。</p><p>关于乱码中出现?或者?，这里需要额外提一下，<strong>当程序使用特定字符编码解析字节流的时候，一旦遇到无法解析的字节流时，就会用?或者?来替代。因此，一旦你最终解析得到的文本包含这样的字符，而你又无法得到原始字节流的时候，说明正确的信息已经彻底丢失了，尝试任何字符编码都无法从这样的字符文本中还原出正确的信息来</strong>。</p><h2>必要的术语解释</h2><p><strong>字符集（Character Set）</strong>，字面上的理解就是字符的集合，例如ASCII字符集，定义了128个字符；GB2312定义了7445个字符。而<strong>计算机系统中提到的字符集准确来说，指的是已编号的字符的有序集合（不一定是连续）</strong>。</p><p><strong>字符码（Code Point）</strong>指的就是字符集中每个字符的数字编号。例如ASCII字符集用0-127这连续的128个数字分别表示128个字符；GBK字符集使用区位码的方式为每个字符编号，首先定义一个94X94的矩阵，行称为“区”，列称为“位”，然后将所有国标汉字放入矩阵当中，这样每个汉字就可以用唯一的“区位”码来标识了。例如“中”字被放到54区第48位，因此字符码就是5448。而Unicode中将字符集按照一定的类别划分到0~16这17个层面（Planes）中，每个层面中拥有2<sup>16</sup>=65536个字符码，因此Unicode总共拥有的字符码，也即是Unicode的字符空间总共有17*65536=1114112。</p><p><sup></sup></p><p><img style="display: inline" title="image" alt="image" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image24.png" width="218" height="199" /></p><p><strong>编码</strong>的过程是将字符转换成字节流。</p><p><strong>解码</strong>的过程是将字节流解析为字符。</p><p><strong>字符编码（Character Encoding）</strong>是将字符集中的字符码映射为字节流的一种具体实现方案。例如ASCII字符编码规定使用单字节中低位的7个比特去编码所有的字符。例如‘A’的编号是65，用单字节表示就是0x41，因此写入存储设备的时候就是b’01000001’。GBK编码则是将区位码（GBK的字符码）中的区码和位码的分别加上0xA0（160）的偏移（之所以要加上这样的偏移，主要是为了和ASCII码兼容），例如刚刚提到的“中”字，区位码是5448，十六进制是0x3630，区码和位码分别加上0xA0的偏移之后就得到0xD6D0，这就是“中”字的GBK编码结果。</p><p><strong>代码页（Code Page）</strong>一种字符编码具体形式。早期字符相对少，因此通常会使用类似表格的形式将字符直接映射为字节流，然后通过查表的方式来实现字符的编解码。现代操作系统沿用了这种方式。例如Windows使用936代码页、Mac系统使用EUC-CN代码页实现GBK字符集的编码，名字虽然不一样，但对于同一汉字的编码肯定是一样的。</p><p><strong>大小端</strong>的说法源自《格列佛游记》。我们知道，鸡蛋通常一端大一端小，小人国的人们对于剥蛋壳时应从哪一端开始剥起有着不一样的看法。同样，计算机界对于传输多字节字（由多个字节来共同表示一个数据类型）时，是先传高位字节（大端）还是先传低位字节（小端）也有着不一样的看法，这就是计算机里头大小端模式的由来了。无论是写文件还是网络传输，实际上都是往流设备进行写操作的过程，而且这个写操作是从流的低地址向高地址开始写（这很符合人的习惯），对于多字节字来说，如果先写入高位字节，则称作大端模式。反之则称作小端模式。也就是说，大端模式下，字节序和流设备的地址顺序是相反的，而小端模式则是相同的。一般网络协议都采用大端模式进行传输。</p><p style="text-align: right">——<a title="关于字符编码，你所需要知道的" href="http://www.imkevinyang.com/2010/06/%e5%85%b3%e4%ba%8e%e5%ad%97%e7%ac%a6%e7%bc%96%e7%a0%81%ef%bc%8c%e4%bd%a0%e6%89%80%e9%9c%80%e8%a6%81%e7%9f%a5%e9%81%93%e7%9a%84.html"><em><strong>Kevin Yang</strong></em></a></p><p>参考链接：</p><ul><li><a title="The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)" href="http://www.joelonsoftware.com/printerFriendly/articles/Unicode.html" target="_blank">The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)</a></li><li><a title="http://developers.sun.com/dev/gadc/technicalpublications/articles/gb18030.html" href="http://developers.sun.com/dev/gadc/technicalpublications/articles/gb18030.html">http://developers.sun.com/dev/gadc/technicalpublications/articles/gb18030.html</a></li><li><a title="http://en.wikipedia.org/wiki/Universal_Character_Set" href="http://en.wikipedia.org/wiki/Universal_Character_Set">http://en.wikipedia.org/wiki/Universal_Character_Set</a></li><li><a title="http://en.wikipedia.org/wiki/Code_page" href="http://en.wikipedia.org/wiki/Code_page">http://en.wikipedia.org/wiki/Code_page</a></li></ul>标签：<a href="http://www.imkevinyang.com/tags/ascii" title="ASCII" rel="tag">ASCII</a>, <a href="http://www.imkevinyang.com/tags/gb18030" title="GB18030" rel="tag">GB18030</a>, <a href="http://www.imkevinyang.com/tags/gb2312" title="GB2312" rel="tag">GB2312</a>, <a href="http://www.imkevinyang.com/tags/gbk" title="GBK" rel="tag">GBK</a>, <a href="http://www.imkevinyang.com/tags/oem" title="OEM" rel="tag">OEM</a>, <a href="http://www.imkevinyang.com/tags/unicode" title="Unicode" rel="tag">Unicode</a>, <a href="http://www.imkevinyang.com/tags/utf-16" title="UTF-16" rel="tag">UTF-16</a>, <a href="http://www.imkevinyang.com/tags/utf-8" title="UTF-8" rel="tag">UTF-8</a>, <a href="http://www.imkevinyang.com/categories/techarticles/othertecharticles" title="其他随笔" rel="tag">其他随笔</a>, <a href="http://www.imkevinyang.com/tags/%e5%ad%97%e7%ac%a6" title="字符" rel="tag">字符</a>, <a href="http://www.imkevinyang.com/tags/%e5%ad%97%e7%ac%a6%e7%bc%96%e8%a7%a3%e7%a0%81" title="字符编解码" rel="tag">字符编解码</a>, <a href="http://www.imkevinyang.com/tags/%e7%bc%96%e7%a0%81" title="编码" rel="tag">编码</a><br /><h4 style="background-color:#3B3B3B;border-bottom:2px groove gray;color:#F2F2F2;margin-top:20px;padding:6px 6px 6px 15px;margin:20px 0px 0px 0px">你可能对下面的文章感兴趣</h4><ul class="st-related-posts"><li><a href="http://www.imkevinyang.com/2009/04/excel%e4%b8%ad%e4%bd%bf%e7%94%a8vba%e8%87%aa%e5%ae%9a%e4%b9%89%e5%87%bd%e6%95%b0%e5%af%b9%e5%ad%97%e7%ac%a6%e4%b8%b2%e7%bb%a7%e7%bb%adurl%e7%bc%96%e7%a0%81.html" title="Excel中使用VBA自定义函数对字符串进行Url编码（UTF-8） (2009/04/28)">Excel中使用VBA自定义函数对字符串进行Url编码（UTF-8）</a> (2009/04/28)</li><li><a href="http://www.imkevinyang.com/2009/05/php%e5%a4%84%e7%90%86bom%e6%a0%87%e8%ae%b0%e7%9a%84utf-8%e6%96%87%e4%bb%b6%e5%af%bc%e8%87%b4%e7%9a%84%e9%97%ae%e9%a2%98.html" title="PHP处理BOM标记的UTF-8文件导致的问题 (2009/05/05)">PHP处理BOM标记的UTF-8文件导致的问题</a> (2009/05/05)</li><li><a href="http://www.imkevinyang.com/2009/02/%e5%ad%97%e7%ac%a6%e7%bc%96%e8%a7%a3%e7%a0%81%e7%9a%84%e6%95%85%e4%ba%8b%ef%bc%88ascii%ef%bc%8cansi%ef%bc%8cunicode%ef%bc%8cutf-8%e5%8c%ba%e5%88%ab%ef%bc%89.html" title="字符编解码的故事（ASCII，ANSI，Unicode，Utf-8区别） (2009/02/28)">字符编解码的故事（ASCII，ANSI，Unicode，Utf-8区别）</a> (2009/02/28)</li><li><a href="http://www.imkevinyang.com/2009/11/%e5%ad%97%e7%ac%a6%ef%bc%8c%e5%ad%97%e8%8a%82%e5%92%8c%e7%bc%96%e7%a0%81.html" title="字符，字节和编码 (2009/11/27)">字符，字节和编码</a> (2009/11/27)</li></ul>]]></content:encoded> <wfw:commentRss>http://www.imkevinyang.com/2010/06/%e5%85%b3%e4%ba%8e%e5%ad%97%e7%ac%a6%e7%bc%96%e7%a0%81%ef%bc%8c%e4%bd%a0%e6%89%80%e9%9c%80%e8%a6%81%e7%9f%a5%e9%81%93%e7%9a%84.html/feed</wfw:commentRss> <slash:comments>2</slash:comments> </item> <item><title>字符，字节和编码</title><link>http://www.imkevinyang.com/2009/11/%e5%ad%97%e7%ac%a6%ef%bc%8c%e5%ad%97%e8%8a%82%e5%92%8c%e7%bc%96%e7%a0%81.html</link> <comments>http://www.imkevinyang.com/2009/11/%e5%ad%97%e7%ac%a6%ef%bc%8c%e5%ad%97%e8%8a%82%e5%92%8c%e7%bc%96%e7%a0%81.html#comments</comments> <pubDate>Fri, 27 Nov 2009 06:17:00 +0000</pubDate> <dc:creator>Kevin Yang</dc:creator> <category><![CDATA[好文分享]]></category> <category><![CDATA[字符]]></category> <category><![CDATA[字符编解码]]></category> <category><![CDATA[字符集]]></category> <category><![CDATA[字节]]></category> <category><![CDATA[编码]]></category><guid isPermaLink="false">http://www.imkevinyang.com/2009/11/%e5%ad%97%e7%ac%a6%ef%bc%8c%e5%ad%97%e8%8a%82%e5%92%8c%e7%bc%96%e7%a0%81.html</guid> <description><![CDATA[<p></p><p>在网上看到的介绍字符编解码的非常不错的文章。</p><p>原文出处：<a href="http://www.regexlab.com/zh/encoding.htm">http://www.regexlab.com/zh/encoding.htm</a></p><h2><a name="intro"></a>引言</h2><p>“字符与编码”是一个被经常讨论的话题。即使这样，时常出现的乱码仍然困扰着大家。虽然我们有很多的办法可以用来消除乱码，但我们并不一定理解这些办法的内在原理。而有的乱码产生的原因，实际上由于底层代码本身有问题所导致的。因此，不仅是初学者会对字符编码感到模糊，有的底层开发人员同样对字符编码缺乏准确的理解。</p><h2>编码&#8230;</h2>]]></description> <content:encoded><![CDATA[<p>在网上看到的介绍字符编解码的非常不错的文章。</p><p>原文出处：<a href="http://www.regexlab.com/zh/encoding.htm">http://www.regexlab.com/zh/encoding.htm</a></p><h2><a name="intro"></a>引言</h2><p>“字符与编码”是一个被经常讨论的话题。即使这样，时常出现的乱码仍然困扰着大家。虽然我们有很多的办法可以用来消除乱码，但我们并不一定理解这些办法的内在原理。而有的乱码产生的原因，实际上由于底层代码本身有问题所导致的。因此，不仅是初学者会对字符编码感到模糊，有的底层开发人员同样对字符编码缺乏准确的理解。</p><h2>编码问题的由来，相关概念的理解</h2><h3>字符与编码的发展</h3><p>从计算机对多国语言的支持角度看，大致可以分为三个阶段：</p><table border="1" cellspacing="0" cellpadding="2"><thead><tr><td style="width: 58px" class="top_1">&#160;</td><td style="width: 88px" class="top_2" nowrap="nowrap" align="center"><b>系统内码</b></td><td class="top_2" align="center"><b>说明</b></td><td class="top_2" align="center"><b>系统</b></td></tr></thead><tbody><tr><td style="width: 58px" class="con_1" nowrap="nowrap">阶段一</td><td style="width: 88px" class="con_2" nowrap="nowrap" align="center">ASCII</td><td class="con_2">计算机刚开始只支持英语，其它语言不能够在计算机上存储和显示。</td><td class="con_2">英文 DOS</td></tr><tr><td style="width: 58px" class="con_1" nowrap="nowrap">阶段二</td><td style="width: 88px" class="con_2" nowrap="nowrap" align="center">ANSI编码 <br />（本地化）</td><td class="con_2">为使计算机支持更多语言，通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如：汉字 '中' 在中文操作系统中，使用 [0xD6,0xD0] 这两个字节存储。</p><p>不同的国家和地区制定了不同的标准，由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式，称为<b>ANSI 编码</b>。在简体中文系统下，ANSI 编码代表 GB2312 编码，在日文操作系统下，ANSI 编码代表 JIS 编码。</p><p>不同 ANSI 编码之间互不兼容，当信息在国际间交流时，无法将属于两种语言的文字，存储在同一段<b> ANSI 编码</b>的文本中。</td><td class="con_2">中文 DOS，中文 Windows 95/98，日文 Windows 95/98</td></tr><tr><td style="width: 58px" class="bot_1" nowrap="nowrap">阶段三</td><td style="width: 88px" class="bot_2" nowrap="nowrap" align="center">UNICODE <br />（国际化）</td><td class="bot_2">为了使国际间信息交流更加方便，国际组织制定了<b>UNICODE 字符集</b>，为各种语言中的每一个字符设定了统一并且唯一的数字编号，以满足跨语言、跨平台进行文本转换、处理的要求。</td><td class="bot_2">Windows NT/2000/XP，Linux，Java</td></tr></tbody></table><p>字符串在内存中的存放方法：</p><p>在 ASCII 阶段，<b>单字节字符串</b>使用一个字节存放一个字符（SBCS）。比如，&quot;Bob123&quot; 在内存中为：</p><table style="color: rgb(0,0,128); font-size: 80%" border="1" cellspacing="5" cellpadding="0"><tbody><tr><td>42</td><td>6F</td><td>62</td><td>31</td><td>32</td><td>33</td><td>00</td></tr><tr style="padding-bottom: 0px; line-height: 1%; border-right-width: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; border-top-width: 0px; border-bottom-width: 0px; font-size: 1px; border-left-width: 0px; padding-top: 0px"><td bgcolor="#000080"><strike></strike></td><td bgcolor="#000080">&#160;</td><td bgcolor="#000080">&#160;</td><td bgcolor="#000080">&#160;</td><td bgcolor="#000080">&#160;</td><td bgcolor="#000080">&#160;</td><td bgcolor="#000080">&#160;</td></tr><tr><td align="center">B</td><td style="text-align: center">o</td><td align="center">b</td><td style="text-align: center">1</td><td align="center">2</td><td align="center">3</td><td align="center">\0</td></tr></tbody></table><p>在使用 ANSI 编码支持多种语言阶段，每个字符使用一个字节或多个字节来表示（MBCS），因此，这种方式存放的字符也被称作<b>多字节字符</b>。比如，&quot;中文123&quot; 在中文 Windows 95 内存中为7个字节，每个汉字占2个字节，每个英文和数字字符占1个字节：</p><table style="color: rgb(0,0,128); font-size: 80%" border="1" cellspacing="5" cellpadding="0"><tbody><tr><td>D6</td><td>D0</td><td>CE</td><td>C4</td><td>31</td><td>32</td><td>33</td><td>00</td></tr><tr style="padding-bottom: 0px; line-height: 1%; border-right-width: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; border-top-width: 0px; border-bottom-width: 0px; font-size: 1px; border-left-width: 0px; padding-top: 0px"><td bgcolor="#ff0000" colspan="2"></td><td bgcolor="#ff0000" colspan="2"></td><td bgcolor="#000080"></td><td style="background-color: rgb(0,0,128)"></td><td bgcolor="#000080"></td><td bgcolor="#000080"></td></tr><tr><td colspan="2" align="center">中</td><td colspan="2" align="center">文</td><td align="center">1</td><td align="center">2</td><td align="center">3</td><td align="center">\0</td></tr></tbody></table><p>在 UNICODE 被采用之后，计算机存放字符串时，改为存放每个字符在 UNICODE 字符集中的序号。目前计算机一般使用 2 个字节（16 位）来存放一个序号（DBCS），因此，这种方式存放的字符也被称作<b>宽字节字符</b>。比如，字符串 &quot;中文123&quot; 在 Windows 2000 下，内存中实际存放的是 5 个序号：</p><table style="color: rgb(0,0,128); font-size: 80%" border="1" cellspacing="5" cellpadding="0"><tbody><tr><td valign="bottom">2D</td><td valign="bottom">4E</td><td valign="bottom">87</td><td valign="bottom">65</td><td valign="bottom">31</td><td valign="bottom">00</td><td valign="bottom">32</td><td valign="bottom">00</td><td valign="bottom">33</td><td valign="bottom">00</td><td valign="bottom">00</td><td valign="bottom">00</td><td><font color="#808080">&#160;&#160;&#160;&#160; ← 在 x86 CPU 中，低字节在前</font></td></tr><tr style="padding-bottom: 0px; line-height: 1%; border-right-width: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; border-top-width: 0px; border-bottom-width: 0px; font-size: 1px; border-left-width: 0px; padding-top: 0px"><td bgcolor="#ff0000" colspan="2"></td><td bgcolor="#ff0000" colspan="2"></td><td bgcolor="#ff0000" colspan="2"></td><td style="background-color: rgb(255,0,0)" colspan="2"></td><td bgcolor="#ff0000" colspan="2"></td><td bgcolor="#ff0000" colspan="2"></td><td></td></tr><tr><td colspan="2" align="center">中</td><td colspan="2" align="center">文</td><td colspan="2" align="center">1</td><td colspan="2" align="center">2</td><td colspan="2" align="center">3</td><td colspan="2" align="center">\0</td><td align="center"></td></tr></tbody></table><p>一共占 10 个字节。</p><h3>字符，字节，字符串</h3><p>理解编码的关键，是要把字符的概念和字节的概念理解准确。这两个概念容易混淆，我们在此做一下区分：</p><table border="1" cellspacing="0" cellpadding="3"><thead><tr><td class="top_1">&#160;</td><td style="width: 600px" class="top_2" align="center"><b>概念描述</b></td><td style="width: 257px" class="top_2" align="center"><b>举例</b></td></tr></thead><tbody><tr><td class="con_1" nowrap="nowrap" align="center">字符</td><td style="width: 600px" class="con_2">人们使用的记号，抽象意义上的一个符号。</td><td style="width: 257px" class="con_2">'1', '中', 'a', '$', '￥', ……</td></tr><tr><td class="con_1" nowrap="nowrap" align="center">字节</td><td style="width: 600px" class="con_2">计算机中存储数据的单元，一个8位的二进制数，是一个很具体的存储空间。</td><td style="width: 257px" class="con_2">0x01, 0x45, 0xFA, ……</td></tr><tr><td class="con_1" nowrap="nowrap" align="center">ANSI <br />字符串</td><td style="width: 600px" class="con_2">在内存中，如果“字符”是以 <b>ANSI 编码</b>形式存在的，一个字符可能使用一个字节或多个字节来表示，那么我们称这种字符串为 <b>ANSI 字符串</b>或者<b>多字节字符串</b>。</td><td style="width: 257px" class="con_2">&quot;中文123&quot; <br /><span class="rem">（占7字节）</span></td></tr><tr><td class="bot_1" nowrap="nowrap" align="center">UNICODE <br />字符串</td><td style="width: 600px" class="bot_2">在内存中，如果“字符”是以在 UNICODE 中的序号存在的，那么我们称这种字符串为 <b>UNICODE 字符串</b>或者<b>宽字节字符串</b>。</td><td style="width: 257px" class="bot_2">L&quot;中文123&quot; <br /><span class="rem">（占10字节）</span></td></tr></tbody></table><p>由于不同 ANSI 编码所规定的标准是不相同的，因此，对于一个给定的<b>多字节字符串</b>，我们必须知道它采用的是哪一种编码规则，才能够知道它包含了哪些“字符”。而对于 <b>UNICODE 字符串</b>来说，不管在什么环境下，它所代表的“字符”内容总是不变的。</p><h3>字符集与编码</h3><p>各个国家和地区所制定的不同 ANSI 编码标准中，都只规定了各自语言所需的“字符”。比如：汉字标准（GB2312）中没有规定韩国语字符怎样存储。这些 ANSI 编码标准所规定的内容包含两层含义：</p><ol><li>使用哪些字符。也就是说哪些汉字，字母和符号会被收入标准中。所包含“字符”的集合就叫做“<b>字符集</b>”。</li><li>规定每个“字符”分别用一个字节还是多个字节存储，用哪些字节来存储，这个规定就叫做“<b>编码</b>”。</li></ol><p>各个国家和地区在制定编码标准的时候，“字符的集合”和“编码”一般都是同时制定的。因此，平常我们所说的“字符集”，比如：GB2312, GBK, JIS 等，除了有“字符的集合”这层含义外，同时也包含了“编码”的含义。</p><p>“<b>UNICODE 字符集</b>”包含了各种语言中使用到的所有“字符”。用来给 UNICODE 字符集编码的标准有很多种，比如：UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等。</p><h3>常用的编码简介</h3><p>简单介绍一下常用的编码规则，为后边的章节做一个准备。在这里，我们根据编码规则的特点，把所有的编码分成三类：</p><table border="1" cellspacing="0" cellpadding="3"><thead><tr><td class="top_1" align="center"><b>分类</b></td><td style="width: 83px" class="top_2" align="center"><b>编码标准</b></td><td style="width: 738px" class="top_2" align="center"><b>说明</b></td></tr></thead><tbody><tr><td class="con_1" nowrap="nowrap" align="center">单字节字符编码</td><td style="width: 83px" class="con_2">ISO-8859-1</td><td style="width: 738px" class="con_2">最简单的编码规则，每一个字节直接作为一个 UNICODE 字符。比如，[0xD6, 0xD0] 这两个字节，通过 iso-8859-1 转化为字符串时，将直接得到 [0x00D6, 0x00D0] 两个 UNICODE 字符，即 &quot;?D&quot;。</p><p>反之，将 UNICODE 字符串通过 iso-8859-1 转化为字节串时，只能正常转化 0~255 范围的字符。</td></tr><tr><td class="con_1" nowrap="nowrap" align="center">ANSI 编码</td><td style="width: 83px" class="con_2">GB2312, <br />BIG5, <br />Shift_JIS, <br />ISO-8859-2 ……</td><td style="width: 738px" class="con_2">把 UNICODE 字符串通过 ANSI 编码转化为“字节串”时，根据各自编码的规定，一个 UNICODE 字符可能转化成一个字节或多个字节。</p><p>反之，将字节串转化成字符串时，也可能多个字节转化成一个字符。比如，[0xD6, 0xD0] 这两个字节，通过 GB2312 转化为字符串时，将得到 [0x4E2D] 一个字符，即 '中' 字。</p><p>“ANSI 编码”的特点： <br />1. 这些“ANSI 编码标准”都只能处理各自语言范围之内的 UNICODE 字符。 <br />2. “UNICODE 字符”与“转换出来的字节”之间的关系是人为规定的。</td></tr><tr><td class="bot_1" nowrap="nowrap" align="center">UNICODE 编码</td><td style="width: 83px" class="bot_2">UTF-8, <br />UTF-16, UnicodeBig ……</td><td style="width: 738px" class="bot_2">与“ANSI 编码”类似的，把字符串通过 UNICODE 编码转化成“字节串”时，一个 UNICODE 字符可能转化成一个字节或多个字节。</p><p>与“ANSI 编码”不同的是： <br />1. 这些“UNICODE 编码”能够处理所有的 UNICODE 字符。 <br />2. “UNICODE 字符”与“转换出来的字节”之间是可以通过计算得到的。</td></tr></tbody></table><p>我们实际上没有必要去深究每一种编码具体把某一个字符编码成了哪几个字节，我们只需要知道“编码”的概念就是把“字符”转化成“字节”就可以了。对于“UNICODE 编码”，由于它们是可以通过计算得到的，因此，在特殊的场合，我们可以去了解某一种“UNICODE 编码”是怎样的规则。</p><h2>字符与编码在程序中的实现</h2><h3>程序中的字符与字节</h3><p>在 C++ 和 Java 中，用来代表“字符”和“字节”的数据类型，以及进行编码的方法：</p><table border="1" cellspacing="0" cellpadding="3"><thead><tr><td class="top_1" align="center"><b>类型或操作</b></td><td class="top_2" align="center"><b>C++</b></td><td class="top_2" align="center"><b>Java</b></td></tr></thead><tbody><tr><td class="con_1" nowrap="nowrap" align="center">字符</td><td class="con_2">wchar_t</td><td class="con_2">char</td></tr><tr><td class="con_1" nowrap="nowrap" align="center">字节</td><td class="con_2">char</td><td class="con_2">byte</td></tr><tr><td class="con_1" nowrap="nowrap" align="center">ANSI 字符串</td><td class="con_2">char[]</td><td class="con_2">byte[]</td></tr><tr><td class="con_1" nowrap="nowrap" align="center">UNICODE 字符串</td><td class="con_2">wchar_t[]</td><td class="con_2">String</td></tr><tr><td class="con_1" nowrap="nowrap" align="center">字节串→字符串</td><td class="con_2">mbstowcs(), MultiByteToWideChar()</td><td class="con_2">string = new String(bytes, &quot;encoding&quot;)</td></tr><tr><td class="bot_1" nowrap="nowrap" align="center">字符串→字节串</td><td class="bot_2">wcstombs(), WideCharToMultiByte()</td><td class="bot_2">bytes = string.getBytes(&quot;encoding&quot;)</td></tr></tbody></table><p>以上需要注意几点：</p><ol><li>Java 中的 char 代表一个“UNICODE 字符（宽字节字符）”，而 C++ 中的 char 代表一个字节。</li><li>MultiByteToWideChar() 和 WideCharToMultiByte() 是 Windows API 函数。</li></ol><h3 align="left">C++ 中相关实现方法</h3><p> 声明一段字符串常量：<br /><table border="1" cellspacing="0" cellpadding="6" bgcolor="#eeeeee"><tbody><tr><td class="code"><span class="rem">// ANSI 字符串，内容长度 7 字节</span><span class="key"> <br />char</span>&#160;&#160;&#160;&#160; sz[<span class="number">20</span>] = <span class="string">&quot;中文123&quot;</span>;</p><p><span class="rem">// UNICODE 字符串，内容长度 5 个 wchar_t（10 字节）</span> <br />wchar_t wsz[<span class="number">20</span>] = L<span class="string">&quot;\x4E2D\x6587\x0031\x0032\x0033&quot;</span>;</td></tr></tbody></table><p>UNICODE 字符串的 I/O 操作，字符与字节的转换操作：</p><table border="1" cellspacing="0" cellpadding="6" bgcolor="#eeeeee"><tbody><tr><td class="code"><span class="rem">// 运行时设定当前 ANSI 编码，VC 格式 <br /></span>setlocale(LC_ALL, <span class="string">&quot;.936&quot;</span>);</p><p><span class="rem">// GCC 中格式</span> <br />setlocale(LC_ALL, <span class="string">&quot;zh_CN.GBK&quot;</span>);</p><p><span class="rem">// Visual C++ 中使用小写 %s，按照 setlocale 指定编码输出到文件 <br />// GCC 中使用大写 %S</span> <br />fwprintf(fp, L<span class="string">&quot;%s\n&quot;</span>, wsz);</p><p><span class="rem">// 把 UNICODE 字符串按照 setlocale 指定的编码转换成字节</span> <br />wcstombs(sz, wsz, <span class="number">20</span>);<span class="rem"> <br />// 把字节串按照 setlocale 指定的编码转换成 UNICODE 字符串 <br /></span>mbstowcs(wsz, sz, <span class="number">20</span>);</td></tr></tbody></table><p>在 Visual C++ 中，UNICODE 字符串常量有更简单的表示方法。如果源程序的编码与当前默认 ANSI 编码不符，则需要使用 #pragma setlocale，告诉编译器源程序使用的编码：</p><table border="1" cellspacing="0" cellpadding="6" bgcolor="#eeeeee"><tbody><tr><td class="code"><span class="rem">// 如果源程序的编码与当前默认 ANSI 编码不一致， <br />// 则需要此行，编译时用来指明当前源程序使用的编码</span><span class="key"> <br />#pragma setlocale</span>(<span class="string">&quot;.936&quot;</span>)</p><p><span class="rem">// UNICODE 字符串常量，内容长度 10 字节</span> <br />wchar_t wsz[<span class="number">20</span>] = L<span class="string">&quot;中文 123&quot;</span>;</td></tr></tbody></table><p>以上需要注意 #pragma setlocale 与 setlocale(LC_ALL, &quot;&quot;) 的作用是不同的，#pragma setlocale 在编译时起作用，setlocale() 在运行时起作用。</p><h3>Java 中相关实现方法</h3><p>字符串类 String 中的内容是 UNICODE 字符串：</p><table border="1" cellspacing="0" cellpadding="6" bgcolor="#eeeeee"><tbody><tr><td class="code"><span class="rem">// Java 代码，直接写中文</span><span class="pw"> <br />String</span> string = <span class="string">&quot;中文123&quot;</span>;</p><p><span class="rem">// 得到长度为 5，因为是 5 个字符</span> <br /><span class="pw">System</span>.out.println(string.length());</td></tr></tbody></table><p>字符串 I/O 操作，字符与字节转换操作。在 Java 包 java.io.* 中，以“Stream”结尾的类一般是用来操作“字节串”的类，以“Reader”，“Writer”结尾的类一般是用来操作“字符串”的类。</p><table border="1" cellspacing="0" cellpadding="6" bgcolor="#eeeeee"><tbody><tr><td class="code"><span class="rem">// 字符串与字节串间相互转化</p><p>// 按照 GB2312 得到字节（得到多字节字符串）</span><span class="key"> <br />byte</span> [] bytes = string.getBytes(<span class="string">&quot;GB2312&quot;</span>);</p><p><span class="rem">// 从字节按照 GB2312 得到 UNICODE 字符串</span> <br />string = <span class="key">new</span> <span class="pw">String</span>(bytes, <span class="string">&quot;GB2312&quot;</span>);</p><p><span class="rem">// 要将 String 按照某种编码写入文本文件，有两种方法：</p><p>// 第一种办法：用 Stream 类写入已经按照指定编码转化好的字节串</span> <br />OutputStream os = <span class="key">new</span> FileOutputStream(<span class="string">&quot;1.txt&quot;</span>); <br />os.write(bytes); <br />os.close();</p><p><span class="rem">// 第二种办法：构造指定编码的 Writer 来写入字符串</span> <br />Writer ow = <span class="key">new</span> OutputStreamWriter(<span class="key">new</span> FileOutputStream(<span class="string">&quot;2.txt&quot;</span>), <span class="string">&quot;GB2312&quot;</span>); <br />ow.write(string); <br />ow.close();</p><p><span class="rem">/* 最后得到的 1.txt 和 2.txt 都是 7 个字节 */</span></td></tr></tbody></table><p>如果 java 的源程序编码与当前默认 ANSI 编码不符，则在编译的时候，需要指明一下源程序的编码。比如：</p><table border="1" cellspacing="0" cellpadding="6" bgcolor="#eeeeee"><tbody><tr><td class="code">E:\&gt;javac <font color="#ff0000">-encoding BIG5</font> Hello.java</td></tr></tbody></table><p>以上需要注意区分源程序的编码与 I/O 操作的编码，前者是在编译时起作用，后者是在运行时起作用。</p><h2>几种误解，以及乱码产生的原因和解决办法</h2><h3>容易产生的误解</h3><table border="1" cellspacing="0" cellpadding="3"><thead><tr><td class="top_1">&#160;</td><td class="top_2" align="center"><b>对编码的误解</b></td></tr></thead><tbody><tr><td class="con_1" nowrap="nowrap" align="center">误解一</td><td class="con_2">在将“字节串”转化成“UNICODE 字符串”时，比如在读取文本文件时，或者通过网络传输文本时，容易将“字节串”简单地作为<b>单字节字符串</b>，采用每“一个字节”就是“一个字符”的方法进行转化。</p><p>而实际上，在非英文的环境中，应该将“字节串”作为 ANSI 字符串，采用适当的编码来得到 UNICODE 字符串，有可能“多个字节”才能得到“一个字符”。</p><p>通常，一直在英文环境下做开发的程序员们，容易有这种误解。</td></tr><tr><td class="bot_1" nowrap="nowrap" align="center">误解二</td><td class="bot_2">在 DOS，Windows 98 等非 UNICODE 环境下，字符串都是以 ANSI 编码的字节形式存在的。这种以字节形式存在的字符串，必须知道是哪种编码才能被正确地使用。这使我们形成了一个惯性思维：“字符串的编码”。</p><p>当 UNICODE 被支持后，Java 中的 String 是以字符的“序号”来存储的，不是以“某种编码的字节”来存储的，因此已经不存在“字符串的编码”这个概念了。只有在“字符串”与“字节串”转化时，或者，将一个“字节串”当成一个 ANSI 字符串时，才有编码的概念。</p><p>不少的人都有这个误解。</td></tr></tbody></table><p>第一种误解，往往是导致乱码产生的原因。第二种误解，往往导致本来容易纠正的乱码问题变得更复杂。</p><p>在这里，我们可以看到，其中所讲的“误解一”，即采用每“一个字节”就是“一个字符”的转化方法，实际上也就等同于采用 iso-8859-1 进行转化。因此，我们常常使用 bytes = string.getBytes(&quot;iso-8859-1&quot;) 来进行逆向操作，得到原始的“字节串”。然后再使用正确的 ANSI 编码，比如 string = new String(bytes, &quot;GB2312&quot;)，来得到正确的“UNICODE 字符串”。</p><h3><a name="instances"></a>非 UNICODE 程序在不同语言环境间移植时的乱码</h3><p>非 UNICODE 程序中的字符串，都是以某种 ANSI 编码形式存在的。如果程序运行时的语言环境与开发时的语言环境不同，将会导致 ANSI 字符串的显示失败。</p><p>比如，在日文环境下开发的非 UNICODE 的日文程序界面，拿到中文环境下运行时，界面上将显示乱码。如果这个日文程序界面改为采用 UNICODE 来记录字符串，那么当在中文环境下运行时，界面上将可以显示正常的日文。</p><p>由于客观原因，有时候我们必须在中文操作系统下运行非 UNICODE 的日文软件，这时我们可以采用一些工具，比如，南极星，AppLocale 等，暂时的模拟不同的语言环境。</p><h3>网页提交字符串</h3><p>当页面中的表单提交字符串时，首先把字符串按照当前页面的编码，转化成字节串。然后再将每个字节转化成 &quot;%XX&quot; 的格式提交到 Web 服务器。比如，一个编码为 GB2312 的页面，提交 &quot;中&quot; 这个字符串时，提交给服务器的内容为 &quot;%D6%D0&quot;。</p><p>在服务器端，Web 服务器把收到的 &quot;%D6%D0&quot; 转化成 [0xD6, 0xD0] 两个字节，然后再根据 GB2312 编码规则得到 &quot;中&quot; 字。</p><p>在 Tomcat 服务器中，request.getParameter() 得到乱码时，常常是因为前面提到的“误解一”造成的。默认情况下，当提交 &quot;%D6%D0&quot; 给 Tomcat 服务器时，request.getParameter() 将返回 [0x00D6, 0x00D0] 两个 UNICODE 字符，而不是返回一个 &quot;中&quot; 字符。因此，我们需要使用 bytes = string.getBytes(&quot;iso-8859-1&quot;) 得到原始的字节串，再用 string = new String(bytes, &quot;GB2312&quot;) 重新得到正确的字符串 &quot;中&quot;。</p><h3>从数据库读取字符串</h3><p>通过数据库客户端（比如 ODBC 或 JDBC）从数据库服务器中读取字符串时，客户端需要从服务器获知所使用的 ANSI 编码。当数据库服务器发送字节流给客户端时，客户端负责将字节流按照正确的编码转化成 UNICODE 字符串。</p><p>如果从数据库读取字符串时得到乱码，而数据库中存放的数据又是正确的，那么往往还是因为前面提到的“误解一”造成的。解决的办法还是通过 string = new String( string.getBytes(&quot;iso-8859-1&quot;), &quot;GB2312&quot;) 的方法，重新得到原始的字节串，再重新使用正确的编码转化成字符串。</p><h3>电子邮件中的字符串</h3><p>当一段 Text 或者 HTML 通过电子邮件传送时，发送的内容首先通过一种指定的<b>字符编码</b>转化成“字节串”，然后再把“字节串”通过一种指定的<b>传输编码</b>（Content-Transfer-Encoding）进行转化得到另一串“字节串”。比如，打开一封电子邮件源代码，可以看到类似的内容：</p><table border="1" cellspacing="0" cellpadding="6" width="100%" bgcolor="#eeeeee"><tbody><tr><td class="code">Content-Type: text/plain; <br />&#160;&#160;&#160;&#160;&#160;&#160;&#160; <font color="#ff0000">charset=&quot;gb2312&quot;</font> <br /><font color="#ff0000">Content-Transfer-Encoding: base64</font></p><p>sbG+qcrQuqO17cf4yee74bGjz9W7+b3wudzA7dbQ0MQNCg0KvPKzxqO6uqO17cnnsaPW0NDEDQoNCg==</td></tr></tbody></table><p>最常用的 Content-Transfer-Encoding 有 Base64 和 Quoted-Printable 两种。在对二进制文件或者中文文本进行转化时，Base64 得到的“字节串”比 Quoted-Printable 更短。在对英文文本进行转化时，Quoted-Printable 得到的“字节串”比 Base64 更短。</p><p>邮件的标题，用了一种更简短的格式来标注“字符编码”和“传输编码”。比如，标题内容为 &quot;中&quot;，则在邮件源代码中表示为：</p><table border="1" cellspacing="0" cellpadding="6" width="100%" bgcolor="#eeeeee"><tbody><tr><td class="code"><span class="rem">// 正确的标题格式</span> <br />Subject: <span style="background-color: rgb(255,255,0)">=?</span>GB2312<span style="background-color: rgb(255,255,0)">?B?</span>1tA=<span style="background-color: rgb(255,255,0)">?=</span></td></tr></tbody></table><p>其中，</p><ul><li>第一个“=?”与“?”中间的部分指定了字符编码，在这个例子中指定的是 GB2312。</li><li>“?”与“?”中间的“B”代表 Base64。如果是“Q”则代表 Quoted-Printable。</li><li>最后“?”与“?=”之间的部分，就是经过 GB2312 转化成字节串，再经过 Base64 转化后的标题内容。</li></ul><p>如果“传输编码”改为 Quoted-Printable，同样，如果标题内容为 &quot;中&quot;：</p><table border="1" cellspacing="0" cellpadding="6" width="100%" bgcolor="#eeeeee"><tbody><tr><td class="code"><span class="rem">// 正确的标题格式</span> <br />Subject: <span style="background-color: rgb(255,255,0)">=?</span>GB2312<span style="background-color: rgb(255,255,0)">?Q?</span>=D6=D0<span style="background-color: rgb(255,255,0)">?=</span></td></tr></tbody></table><p>如果阅读邮件时出现乱码，一般是因为“字符编码”或“传输编码”指定有误，或者是没有指定。比如，有的发邮件组件在发送邮件时，标题 &quot;中&quot;：</p><table border="1" cellspacing="0" cellpadding="6" width="100%" bgcolor="#eeeeee"><tbody><tr><td class="code"><span class="rem">// 错误的标题格式</span> <br />Subject: <span style="background-color: rgb(255,255,0)">=?</span><font color="#ff0000">ISO-8859-1</font><span style="background-color: rgb(255,255,0)">?Q?</span>=D6=D0<span style="background-color: rgb(255,255,0)">?=</span></td></tr></tbody></table><p>这样的表示，实际上是明确指明了标题为 [0x00D6, 0x00D0]，即 &quot;?D&quot;，而不是 &quot;中&quot;。</p><h2>几种错误理解的纠正</h2><h5>误解：“ISO-8859-1 是国际编码？”</h5><p>非也。iso-8859-1 只是单字节字符集中最简单的一种，也就是“字节编号”与“UNICODE 字符编号”一致的那种编码规则。当我们要把一个“字节串”转化成“字符串”，而又不知道它是哪一种 ANSI 编码时，先暂时地把“每一个字节”作为“一个字符”进行转化，不会造成信息丢失。然后再使用 bytes = string.getBytes(&quot;iso-8859-1&quot;) 的方法可恢复到原始的字节串。</p><h5>误解：“Java 中，怎样知道某个字符串的内码？”</h5><p>Java 中，字符串类 java.lang.String 处理的是 UNICODE 字符串，不是 ANSI 字符串。我们只需要把字符串作为“抽象的符号的串”来看待。因此不存在字符串的内码的问题。</p>标签：<a href="http://www.imkevinyang.com/categories/greatpoststoshare" title="好文分享" rel="tag">好文分享</a>, <a href="http://www.imkevinyang.com/tags/%e5%ad%97%e7%ac%a6" title="字符" rel="tag">字符</a>, <a href="http://www.imkevinyang.com/tags/%e5%ad%97%e7%ac%a6%e7%bc%96%e8%a7%a3%e7%a0%81" title="字符编解码" rel="tag">字符编解码</a>, <a href="http://www.imkevinyang.com/tags/%e5%ad%97%e7%ac%a6%e9%9b%86" title="字符集" rel="tag">字符集</a>, <a href="http://www.imkevinyang.com/tags/%e5%ad%97%e8%8a%82" title="字节" rel="tag">字节</a>, <a href="http://www.imkevinyang.com/tags/%e7%bc%96%e7%a0%81" title="编码" rel="tag">编码</a><br /><h4 style="background-color:#3B3B3B;border-bottom:2px groove gray;color:#F2F2F2;margin-top:20px;padding:6px 6px 6px 15px;margin:20px 0px 0px 0px">你可能对下面的文章感兴趣</h4><ul class="st-related-posts"><li><a href="http://www.imkevinyang.com/2009/11/asp-net%e9%a1%b5%e9%9d%a2%e7%9a%84%e7%bc%96%e7%a0%81%e9%97%ae%e9%a2%98.html" title="Asp.Net页面的编码问题 (2009/11/27)">Asp.Net页面的编码问题</a> (2009/11/27)</li><li><a href="http://www.imkevinyang.com/2010/06/%e5%85%b3%e4%ba%8e%e5%ad%97%e7%ac%a6%e7%bc%96%e7%a0%81%ef%bc%8c%e4%bd%a0%e6%89%80%e9%9c%80%e8%a6%81%e7%9f%a5%e9%81%93%e7%9a%84.html" title="关于字符编码，你所需要知道的 (2010/06/18)">关于字符编码，你所需要知道的</a> (2010/06/18)</li><li><a href="http://www.imkevinyang.com/2009/02/%e5%ad%97%e7%ac%a6%e7%bc%96%e8%a7%a3%e7%a0%81%e7%9a%84%e6%95%85%e4%ba%8b%ef%bc%88ascii%ef%bc%8cansi%ef%bc%8cunicode%ef%bc%8cutf-8%e5%8c%ba%e5%88%ab%ef%bc%89.html" title="字符编解码的故事（ASCII，ANSI，Unicode，Utf-8区别） (2009/02/28)">字符编解码的故事（ASCII，ANSI，Unicode，Utf-8区别）</a> (2009/02/28)</li><li><a href="http://www.imkevinyang.com/2009/08/%e6%96%87%e6%a1%a3%e5%ad%97%e7%ac%a6%e9%9b%86%e5%af%bc%e8%87%b4%e7%9a%84%e8%84%9a%e6%9c%ac%e9%94%99%e8%af%af.html" title="文档字符集导致的脚本错误 (2009/08/19)">文档字符集导致的脚本错误</a> (2009/08/19)</li><li><a href="http://www.imkevinyang.com/2009/09/%e8%a7%a3%e5%86%b3xaml%e4%b8%ad%e5%8c%85%e5%90%ab%e4%b8%ad%e6%96%87%e5%af%bc%e8%87%b4%e6%97%a0%e6%b3%95%e7%bc%96%e8%af%91%e7%9a%84%e9%97%ae%e9%a2%98.html" title="解决Xaml中包含中文导致无法编译的问题 (2009/09/29)">解决Xaml中包含中文导致无法编译的问题</a> (2009/09/29)</li><li><a href="http://www.imkevinyang.com/2009/08/%e8%af%a6%e8%a7%a3javascript%e4%b8%ad%e7%9a%84url%e7%bc%96%e8%a7%a3%e7%a0%81.html" title="详解Javascript中的Url编码/解码 (2009/08/19)">详解Javascript中的Url编码/解码</a> (2009/08/19)</li></ul>]]></content:encoded> <wfw:commentRss>http://www.imkevinyang.com/2009/11/%e5%ad%97%e7%ac%a6%ef%bc%8c%e5%ad%97%e8%8a%82%e5%92%8c%e7%bc%96%e7%a0%81.html/feed</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>字符编解码的故事（ASCII，ANSI，Unicode，Utf-8区别）</title><link>http://www.imkevinyang.com/2009/02/%e5%ad%97%e7%ac%a6%e7%bc%96%e8%a7%a3%e7%a0%81%e7%9a%84%e6%95%85%e4%ba%8b%ef%bc%88ascii%ef%bc%8cansi%ef%bc%8cunicode%ef%bc%8cutf-8%e5%8c%ba%e5%88%ab%ef%bc%89.html</link> <comments>http://www.imkevinyang.com/2009/02/%e5%ad%97%e7%ac%a6%e7%bc%96%e8%a7%a3%e7%a0%81%e7%9a%84%e6%95%85%e4%ba%8b%ef%bc%88ascii%ef%bc%8cansi%ef%bc%8cunicode%ef%bc%8cutf-8%e5%8c%ba%e5%88%ab%ef%bc%89.html#comments</comments> <pubDate>Sun, 01 Mar 2009 00:58:15 +0000</pubDate> <dc:creator>Kevin Yang</dc:creator> <category><![CDATA[好文分享]]></category> <category><![CDATA[ANSI]]></category> <category><![CDATA[ASCII]]></category> <category><![CDATA[Unicode]]></category> <category><![CDATA[UTF-8]]></category> <category><![CDATA[字符编解码]]></category> <category><![CDATA[字符集]]></category> <category><![CDATA[编解码]]></category><guid isPermaLink="false">http://www.imkevinyang.com/?p=36</guid> <description><![CDATA[<p><font color="#ff0000">此文为转载，有少许修订，原文出处不详。</font></p><p>很久很久以前，有一群人，他们决定用8个可以开合的晶体管来组合成不同的状态，以表示世界上的万物。他们认为8个开关状态作为原子单位很好，于是他们把这称为&#34;字节&#34;。</p><p>再后来，他们又做了一些可以处理这些字节的机器，机器开动了，可以用字节来组合出更多的状态，状态开始变来变去。他们看到这样是好的，于是它们就这机器称为&#34;计算机&#34;。</p><p>开始计算机只在美国用。八位的字节一共可以组合出&#8230;</p>]]></description> <content:encoded><![CDATA[<p><font color="#ff0000">此文为转载，有少许修订，原文出处不详。</font></p><p>很久很久以前，有一群人，他们决定用8个可以开合的晶体管来组合成不同的状态，以表示世界上的万物。他们认为8个开关状态作为原子单位很好，于是他们把这称为&quot;字节&quot;。</p><p>再后来，他们又做了一些可以处理这些字节的机器，机器开动了，可以用字节来组合出更多的状态，状态开始变来变去。他们看到这样是好的，于是它们就这机器称为&quot;计算机&quot;。</p><p>开始计算机只在美国用。八位的字节一共可以组合出256（2的8次方）种不同的状态。</p><p>他们把其中的编号从0开始的32种状态分别规定了特殊的用途，一但终端设备或者打印机遇上这些约定好的字节时，就要做一些约定的动作。遇上 00x10, 终端就换行，遇上0x07, 终端就向人们嘟嘟叫，例好遇上0x1b, 打印机就打印反白的字，对于终端就用彩色显示字母。他们看到这样很好，于是就把这些0x20（十进制32）以下的字节状态称为&quot;控制码&quot;。</p><p>他们又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示，一直编到了第127号，这样计算机就可以用不同字节来存储英语的 文字了。大家看到这样，都感觉很好，于是大家都把这个方案叫做 ANSI 的&quot;Ascii&quot;编码（American Standard Code for Information Interchange，美国信息互换标准代码）。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。</p><p>后来，就像建造巴比伦塔一样，世界各地的都开始使用计算机，但是很多国家用的不是英文，他们用到的许多字母在ASCII中根本没有，为了也可以在计算机中保存他们的文字，他们决定采用127号之后的空位来表示这些新的字母、符号，还加入了很多画表格时需要用下到的横线、竖线、交叉等形状，一直把序号编到了最后一个状态255。从128到255这一页的字符集被称&quot;扩展字符集&quot;。从此之后，贪婪的人类再没有新的状态可以用了，美帝国主义可能没有想到还有第三世界国家的人们也希望可以用到计算机吧！</p><p>等中国人们得到计算机时，已经没有可以利用的字节状态来表示汉字，况且有6000多个常用汉字需要保存呢。但是这难不倒智慧的中国人民，我们不客气地把那些127号之后的奇异符号们直接取消掉，并且规定：一个小于127的字符的意义与原来相同，但两个大于127的字符连在一起时，就表示一个汉字，前面的一个字节（他称之为高字节）从0xA1用到 0xF7，后面一个字节（低字节）从0xA1到0xFE，这样我们就可以组合出大约7000多个简体汉字了。在这些编码里，我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了，连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码，这就是常说的&quot;全角&quot;字符，而原来在127号以下的那些就叫&quot;半角&quot;字符了。</p><p>中国人民看到这样很不错，于是就把这种汉字方案叫做&quot;GB2312&quot;。GB2312 是对 ASCII 的中文扩展。</p><p>但是中国的汉字太多了，我们很快就就发现有许多人的人名没有办法在这里打出来，特别是某些很会麻烦别人的国家领导人（如朱镕基的“镕”字）。于是我们不得不继续把 GB2312 没有用到的码位找出来老实不客气地用上。</p><p>后来还是不够用，于是干脆不再要求低字节一定是127号之后的内码，只要第一个字节是大于127就固定表示这是一个汉字的开始，不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准，GBK 包括了 GB2312 的所有内容，同时又增加了近20000个新的汉字（包括繁体字）和符号。</p><p>后来少数民族也要用电脑了，于是我们再扩展，又加了几千个新的少数民族的字，GBK 扩成了 GB18030。从此之后，中华民族的文化就可以在计算机时代中传承了。</p><p>中国的程序员们看到这一系列汉字编码的标准是好的，于是通称他们叫做 &quot;DBCS&quot;（Double Byte Charecter Set 双字节字符集）。在DBCS系列标准里，最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里，因此他们写的程序为了支持中文处理，必须要注意字串里的每一个字节的值，如果这个值是大于127的，那么就认为一个双字节字符集里的字符出现了。那时候凡是受过加持，会编程的计算机僧侣们都要每天念下面这个咒语数百遍：</p><p>&quot;一个汉字算两个英文字符！一个汉字算两个英文字符……&quot;</p><p>因为当时各个国家都像中国这样搞出一套自己的编码标准，结果互相之间谁也不懂谁的编码，谁也不支持别人的编码，连大陆和台湾这样只相隔了150海里，使用着同一种语言的兄弟地区，也分别采用了不同的 DBCS 编码方案——当时的中国人想让电脑显示汉字，就必须装上一个&quot;汉字系统&quot;，专门用来处理汉字的显示、输入的问题，但是那个台湾的愚昧封建人士写的算命程序就必须加装另一套支持 BIG5 编码的什么&quot;倚天汉字系统&quot;才可以用，装错了字符系统，显示就会乱了套！这怎么办？而且世界民族之林中还有那些一时用不上电脑的穷苦人民，他们的文字又怎么办？</p><p>真是计算机的巴比伦塔命题啊！</p><p>正在这时，大天使加百列及时出现了——一个叫 ISO （国际标谁化组织）的国际组织决定着手解决这个问题。他们采用的方法很简单：废了所有的地区性编码方案，重新搞一个包括了地球上所有文化、所有字母和符号的编码！他们打算叫它&quot;Universal Multiple-Octet Coded Character Set&quot;，简称 UCS, 俗称 &quot;UNICODE&quot;。</p><p>UNICODE 开始制订时，计算机的存储器容量极大地发展了，空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节，也就是16位来统一表示所有的字符，对于ascii里的那些&quot;半角&quot;字符，UNICODE 包持其原编码不变，只是将其长度由原来的8位扩展为16位，而其他文化和语言的字符则全部重新统一编码。由于&quot;半角&quot;英文符号只需要用到低8位，所以其高 8位永远是0，因此这种大气的方案在保存英文文本时会多浪费一倍的空间。</p><p>这时候，从旧社会里走过来的程序员开始发现一个奇怪的现象：他们的strlen函数靠不住了，一个汉字不再是相当于两个字符了，而是一个！是 的，从 UNICODE 开始，无论是半角的英文字母，还是全角的汉字，它们都是统一的&quot;一个字符&quot;！同时，也都是统一的&quot;两个字节&quot;，请注意&quot;字符&quot;和&quot;字节&quot;两个术语的不同， &quot;字节&quot;是一个8位的物理存贮单元，而&quot;字符&quot;则是一个文化相关的符号。在UNICODE 中，一个字符就是两个字节。一个汉字算两个英文字符的时代已经快过去了。</p><p>从前多种字符集存在时，那些做多语言软件的公司遇上过很大麻烦，他们为了在不同的国家销售同一套软件，就不得不在区域化软件时也加持那个双字节字符集咒语，不仅要处处小心不要搞错，还要把软件中的文字在不同的字符集中转来转去。UNICODE 对于他们来说是一个很好的一揽子解决方案，于是从 Windows NT 开始，MS 趁机把它们的操作系统改了一遍，把所有的核心代码都改成了用 UNICODE 方式工作的版本，从这时开始，WINDOWS 系统终于无需要加装各种本土语言系统，就可以显示全世界上所有文化的字符了。</p><p>但是，UNICODE 在制订时没有考虑与任何一种现有的编码方案保持兼容，这使得 GBK 与UNICODE 在汉字的内码编排上完全是不一样的，没有一种简单的算术方法可以把文本内容从UNICODE编码和另一种编码进行转换，这种转换必须通过查表来进行。</p><p>如前所述，UNICODE 是用两个字节来表示为一个字符，他总共可以组合出65535不同的字符，这大概已经可以覆盖世界上所有文化的符号。如果还不够也没有关系，ISO已经准备了UCS-4方案，说简单了就是四个字节来表示一个字符，这样我们就可以组合出21亿个不同的字符出来（最高位有其他用途），这大概可以用到银河联邦成立那一天吧！</p><p>UNICODE 来到时，一起到来的还有计算机网络的兴起，UNICODE 如何在网络上传输也是一个必须考虑的问题，于是面向传输的众多 UTF（UCS Transfer Format）标准出现了，顾名思义，UTF8就是每次8个位传输数据，而UTF16就是每次16个位，只不过为了传输时的可靠性，从UNICODE到 UTF时并不是直接的对应，而是要过一些算法和规则来转换。</p><p>受到过网络编程加持的计算机僧侣们都知道，在网络里传递信息时有一个很重要的问题，就是对于数据高低位的解读方式，一些计算机是采用低位先发送的方法，例如我们PC机采用的 INTEL 架构；而另一些是采用高位先发送的方式。在网络中交换数据时，为了核对双方对于高低位的认识是否是一致的，采用了一种很简便的方法，就是在文本流的开始时向对方发送一个标志符——如果之后的文本是高位在位，那就发送&quot;FEFF&quot;，反之，则发送&quot;FFFE&quot;。不信你可以用二进制方式打开一个UTF-X格式的文件，看看开头两个字节是不是这两个字节？</p><p>下面是Unicode和UTF-8转换的规则</p><blockquote><p>Unicode</p><p>UTF-8</p><p>0000 - 007F</p><p>0xxxxxxx</p><p>0080 - 07FF</p><p>110xxxxx 10xxxxxx</p><p>0800 - FFFF</p><p>1110xxxx 10xxxxxx 10xxxxxx</p></blockquote><p>例如&quot;汉&quot;字的Unicode编码是6C49。6C49在0800-FFFF之间，所以要用3字节模板：1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是：0110 1100 0100 1001，将这个比特流按三字节模板的分段方法分为0110 110001 001001，依次代替模板中的x，得到：1110-0110 10-110001 10-001001，即E6 B1 89，这就是其UTF8的编码。</p><p>讲到这里，我们再顺便说说一个很著名的奇怪现象：当你在 windows 的记事本里新建一个文件，输入&quot;联通&quot;两个字之后，保存，关闭，然后再次打开，你会发现这两个字已经消失了，代之的是几个乱码！呵呵，有人说这就是联通之所以拼不过移动的原因。</p><p>其实这是因为GB2312编码与UTF8编码产生了编码冲撞的原因。</p><p>当一个软件打开一个文本时，它要做的第一件事是决定这个文本究竟是使用哪种字符集的哪种编码保存的。软件一般采用三种方式来决定文本的字符集和编码：</p><p>检测文件头标识，提示用户选择，根据一定的规则猜测</p><p>最标准的途径是检测文本最开头的几个字节，开头字节 Charset/encoding,如下表：</p><blockquote><p>EF BB BF UTF-8</p><p>FE FF UTF-16/UCS-2, little endian</p><p>FF FE UTF-16/UCS-2, big endian</p><p>FF FE 00 00 UTF-32/UCS-4, little endian.</p><p>00 00 FE FF UTF-32/UCS-4, big-endian.</p></blockquote><p>当你新建一个文本文件时，记事本的编码默认是ANSI（代表系统默认编码，在中文系统中一般是GB系列编码）, 如果你在ANSI的编码输入汉字，那么他实际就是GB系列的编码方式，在这种编码下，&quot;联通&quot;的内码是：</p><blockquote><p>c1 1100 0001</p><p>aa 1010 1010</p><p>cd 1100 1101</p><p>a8 1010 1000</p></blockquote><p>注意到了吗？第一二个字节、第三四个字节的起始部分的都是&quot;110&quot;和&quot;10&quot;，正好与UTF8规则里的两字节模板是一致的，</p><p>于是当我们再次打开记事本时，记事本就误认为这是一个UTF8编码的文件，让我们把第一个字节的110和第二个字节的10去掉，我们就得到了&quot;00001 101010&quot;，再把各位对齐，补上前导的0，就得到了&quot;0000 0000 0110 1010&quot;，不好意思，这是UNICODE的006A，也就是小写的字母&quot;j&quot;，而之后的两字节用UTF8解码之后是0368，这个字符什么也不是。这就是只有&quot;联通&quot;两个字的文件没有办法在记事本里正常显示的原因。</p><p>而如果你在&quot;联通&quot;之后多输入几个字，其他的字的编码不见得又恰好是110和10开始的字节，这样再次打开时，记事本就不会坚持这是一个utf8编码的文件，而会用ANSI的方式解读之，这时乱码又不出现了。</p>标签：<a href="http://www.imkevinyang.com/tags/ansi" title="ANSI" rel="tag">ANSI</a>, <a href="http://www.imkevinyang.com/tags/ascii" title="ASCII" rel="tag">ASCII</a>, <a href="http://www.imkevinyang.com/tags/unicode" title="Unicode" rel="tag">Unicode</a>, <a href="http://www.imkevinyang.com/tags/utf-8" title="UTF-8" rel="tag">UTF-8</a>, <a href="http://www.imkevinyang.com/categories/greatpoststoshare" title="好文分享" rel="tag">好文分享</a>, <a href="http://www.imkevinyang.com/tags/%e5%ad%97%e7%ac%a6%e7%bc%96%e8%a7%a3%e7%a0%81" title="字符编解码" rel="tag">字符编解码</a>, <a href="http://www.imkevinyang.com/tags/%e5%ad%97%e7%ac%a6%e9%9b%86" title="字符集" rel="tag">字符集</a>, <a href="http://www.imkevinyang.com/tags/%e7%bc%96%e8%a7%a3%e7%a0%81" title="编解码" rel="tag">编解码</a><br /><h4 style="background-color:#3B3B3B;border-bottom:2px groove gray;color:#F2F2F2;margin-top:20px;padding:6px 6px 6px 15px;margin:20px 0px 0px 0px">你可能对下面的文章感兴趣</h4><ul class="st-related-posts"><li><a href="http://www.imkevinyang.com/2009/11/asp-net%e9%a1%b5%e9%9d%a2%e7%9a%84%e7%bc%96%e7%a0%81%e9%97%ae%e9%a2%98.html" title="Asp.Net页面的编码问题 (2009/11/27)">Asp.Net页面的编码问题</a> (2009/11/27)</li><li><a href="http://www.imkevinyang.com/2009/04/excel%e4%b8%ad%e4%bd%bf%e7%94%a8vba%e8%87%aa%e5%ae%9a%e4%b9%89%e5%87%bd%e6%95%b0%e5%af%b9%e5%ad%97%e7%ac%a6%e4%b8%b2%e7%bb%a7%e7%bb%adurl%e7%bc%96%e7%a0%81.html" title="Excel中使用VBA自定义函数对字符串进行Url编码（UTF-8） (2009/04/28)">Excel中使用VBA自定义函数对字符串进行Url编码（UTF-8）</a> (2009/04/28)</li><li><a href="http://www.imkevinyang.com/2009/05/php%e5%a4%84%e7%90%86bom%e6%a0%87%e8%ae%b0%e7%9a%84utf-8%e6%96%87%e4%bb%b6%e5%af%bc%e8%87%b4%e7%9a%84%e9%97%ae%e9%a2%98.html" title="PHP处理BOM标记的UTF-8文件导致的问题 (2009/05/05)">PHP处理BOM标记的UTF-8文件导致的问题</a> (2009/05/05)</li><li><a href="http://www.imkevinyang.com/2010/06/%e5%85%b3%e4%ba%8e%e5%ad%97%e7%ac%a6%e7%bc%96%e7%a0%81%ef%bc%8c%e4%bd%a0%e6%89%80%e9%9c%80%e8%a6%81%e7%9f%a5%e9%81%93%e7%9a%84.html" title="关于字符编码，你所需要知道的 (2010/06/18)">关于字符编码，你所需要知道的</a> (2010/06/18)</li><li><a href="http://www.imkevinyang.com/2009/11/%e5%ad%97%e7%ac%a6%ef%bc%8c%e5%ad%97%e8%8a%82%e5%92%8c%e7%bc%96%e7%a0%81.html" title="字符，字节和编码 (2009/11/27)">字符，字节和编码</a> (2009/11/27)</li><li><a href="http://www.imkevinyang.com/2009/08/%e6%96%87%e6%a1%a3%e5%ad%97%e7%ac%a6%e9%9b%86%e5%af%bc%e8%87%b4%e7%9a%84%e8%84%9a%e6%9c%ac%e9%94%99%e8%af%af.html" title="文档字符集导致的脚本错误 (2009/08/19)">文档字符集导致的脚本错误</a> (2009/08/19)</li><li><a href="http://www.imkevinyang.com/2009/09/%e8%a7%a3%e5%86%b3xaml%e4%b8%ad%e5%8c%85%e5%90%ab%e4%b8%ad%e6%96%87%e5%af%bc%e8%87%b4%e6%97%a0%e6%b3%95%e7%bc%96%e8%af%91%e7%9a%84%e9%97%ae%e9%a2%98.html" title="解决Xaml中包含中文导致无法编译的问题 (2009/09/29)">解决Xaml中包含中文导致无法编译的问题</a> (2009/09/29)</li><li><a href="http://www.imkevinyang.com/2009/08/%e8%af%a6%e8%a7%a3javascript%e4%b8%ad%e7%9a%84url%e7%bc%96%e8%a7%a3%e7%a0%81.html" title="详解Javascript中的Url编码/解码 (2009/08/19)">详解Javascript中的Url编码/解码</a> (2009/08/19)</li></ul>]]></content:encoded> <wfw:commentRss>http://www.imkevinyang.com/2009/02/%e5%ad%97%e7%ac%a6%e7%bc%96%e8%a7%a3%e7%a0%81%e7%9a%84%e6%95%85%e4%ba%8b%ef%bc%88ascii%ef%bc%8cansi%ef%bc%8cunicode%ef%bc%8cutf-8%e5%8c%ba%e5%88%ab%ef%bc%89.html/feed</wfw:commentRss> <slash:comments>0</slash:comments> </item> </channel> </rss>