<?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%e9%9b%86/feed" rel="self" type="application/rss+xml" />
	<link>http://www.imkevinyang.com</link>
	<description>It&#039;s all about sharing</description>
	<lastBuildDate>Sun, 05 Feb 2012 15:37:14 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312&#8230;）</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>
		<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>
还是得从ASCII码说起
<p>说到字符编码，不得不说ASCII码的简史。计算机一开始发明的时候是用来解决数字计算的问题，后来人们发现，计算机还可以做更多的事，例如文本&#8230;</p>]]></description>
			<content:encoded><![CDATA[<p>字符编码的问题看似很小，经常被技术人员忽视，但是很容易导致一些莫名其妙的问题。这里总结了一下字符编码的一些普及性的知识，希望对大家有所帮助。</p>
<h2>还是得从ASCII码说起</h2>
<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;" src="http://www.joelonsoftware.com/pictures/unicode/ascii.png" alt="ASCII table" 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字符集" src="http://www.joelonsoftware.com/pictures/unicode/oem.png" alt="IBM-PC OEM字符集" width="271" height="209" /></p>
<p>事实上，大部分OEM字符集是兼容ASCII字符集的，也就是说，大家对于0x00~0x7F这个范围的解释基本是相同的，而对于后半部分0x80~0xFF的解释却不一定相同。甚至有时候同样的字符在不同OEM字符集中对应的字节也是不同的。</p>
<p>不同的OEM字符集导致人们无法跨机器交流各种文档。例如职员甲发了一封简历résumés给职员乙，结果职员乙看到的却是r<img src="http://www.joelonsoftware.com/pictures/unicode/gimel.png" alt="?" width="5" height="9" />sum<img src="http://www.joelonsoftware.com/pictures/unicode/gimel.png" alt="?" width="5" height="9" />s，因为é字符在职员甲机器上的OEM字符集中对应的字节是0x82，而在职员乙的机器上，由于使用的OEM字符集不同，对0x82字节解码后得到的字符却是<img src="http://www.joelonsoftware.com/pictures/unicode/gimel.png" alt="?" 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" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image15.png" alt="image" 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" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image9.png" alt="image" 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" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image29.png" alt="image" 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" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image17.png" alt="image" width="373" height="275" /></p>
<p>这种方式的缺点在于，字符和字节流之间耦合得太紧密了，从而限定了字符集的扩展能力。假设以后火星人入住地球了，要往现有字符集中加入火星文就变得很难甚至不可能了，而且很容易破坏现有的编码规则。</p>
<p>因此Unicode在设计上考虑到了这一点，将字符集和字符编码方案分离开。</p>
<p><img style="display: inline;" title="字符编码系统" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image10.png" alt="字符编码系统" 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" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image11.png" alt="image" 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>
<h2>乱码问题</h2>
<p>乱码指的是程序显示出来的字符文本无法用任何语言去解读。一般情况下会包含大量<img class="size-full wp-image-1847" title="symbol" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/symbol.jpg" alt="解码失败替换字符" width="16" height="17" />或者?。乱码问题是所有计算机用户或多或少会遇到的问题。<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" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image12.png" alt="image" width="209" height="121" /> <img style="display: inline;" title="image" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image51.png" alt="image" 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>当程序使用特定字符编码解析字节流的时候，一旦遇到无法解析的字节流时，就会用<img title="symbol" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/symbol.jpg" alt="解码失败替换字符" width="16" height="17" />或者?来替代。因此，一旦你最终解析得到的文本包含这样的字符，而你又无法得到原始字节流的时候，说明正确的信息已经彻底丢失了，尝试任何字符编码都无法从这样的字符文本中还原出正确的信息来</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" src="http://www.imkevinyang.com/wp-content/uploads/2010/06/image24.png" alt="image" 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>的说法源自《格列佛游记》。我们知道，鸡蛋通常一端大一端小，小人国的人们对于剥蛋壳时应从哪一端开始剥起有着不一样的看法。同样，计算机界对于传输多字节字（由多个字节来共同表示一个数据类型）时，是先传高位字节（大端）还是先传低位字节（小端）也有着不一样的看法，这就是计算机里头大小端模式的由来了。无论是写文件还是网络传输，实际上都是往流设备进行写操作的过程，而且这个写操作是从流的低地址向高地址开始写（这很符合人的习惯），对于多字节字来说，如果先写入高位字节，则称作大端模式。反之则称作小端模式。也就是说，大端模式下，字节序和流设备的地址顺序是相反的，而小端模式则是相同的。一般网络协议都采用大端模式进行传输，windows操作系统采用Utf-16小端模式。</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/%e5%ad%97%e7%ac%a6%e9%9b%86" 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> </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> </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> </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> </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> </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> </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> </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> </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>6</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><span style="color: #ff0000;">（关于字符编码的深入解释，请参见我的原创文章<strong><a title="关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312…）" 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" target="_self">关于字符编码，你所需要知道的</a>.）</strong></span></p>
<p>在网上看到的介绍字符编解码的非常不错的文章。</p>
<p>原文出处：<a href="http://www.regexlab.com/zh/encoding.htm">http://www.regexlab.com/zh/encoding.htm</a></p>
<a name="intro"></a>引言
<p>“字符与编码”是一个被经常讨论的话题。即使这样，时常出现的乱码仍然困扰着大家。虽然我们有&#8230;</p>]]></description>
			<content:encoded><![CDATA[<p><!--<!  .code { font-family: lucida console, simsun, ms gothic, new gulim; line-height:130%; word-wrap: break-word; word-break:break-all; } .key { color: #0000ff } .h_attr { color: #ff0000 } .h_value { color: #0000ff } .h_tag { color: #800080 } .h_rem { color: #bbbbbb } .h_amps { color: #6d66a5 } .pw { color: #ff0000 } .var { color: #800080 } .number { color: #6d66a5 } .string { color: #ff00ff } .rem { color: #339933 } --><span style="color: #ff0000;">（关于字符编码的深入解释，请参见我的原创文章<strong><a title="关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312…）" 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" target="_self">关于字符编码，你所需要知道的</a>.）</strong></span></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>编码问题的由来，相关概念的理解</h2>
<h3>字符与编码的发展</h3>
<p>从计算机对多国语言的支持角度看，大致可以分为三个阶段：</p>
<table border="1" cellspacing="0" cellpadding="2">
<thead>
<tr>
<td class="top_1" style="width: 58px;"></td>
<td class="top_2" style="width: 88px;" align="center"><strong>系统内码</strong></td>
<td class="top_2" align="center"><strong>说明</strong></td>
<td class="top_2" align="center"><strong>系统</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td class="con_1" style="width: 58px;">阶段一</td>
<td class="con_2" style="width: 88px;" align="center">ASCII</td>
<td class="con_2">计算机刚开始只支持英语，其它语言不能够在计算机上存储和显示。</td>
<td class="con_2">英文 DOS</td>
</tr>
<tr>
<td class="con_1" style="width: 58px;">阶段二</td>
<td class="con_2" style="width: 88px;" align="center">ANSI编码<br />
（本地化）</td>
<td class="con_2">为使计算机支持更多语言，通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如：汉字 '中' 在中文操作系统中，使用 [0xD6,0xD0] 这两个字节存储。</p>
<p>不同的国家和地区制定了不同的标准，由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式，称为<strong>ANSI 编码</strong>。在简体中文系统下，ANSI 编码代表 GB2312 编码，在日文操作系统下，ANSI 编码代表 JIS 编码。</p>
<p>不同 ANSI 编码之间互不兼容，当信息在国际间交流时，无法将属于两种语言的文字，存储在同一段<strong> ANSI 编码</strong>的文本中。</td>
<td class="con_2">中文 DOS，中文 Windows 95/98，日文 Windows 95/98</td>
</tr>
<tr>
<td class="bot_1" style="width: 58px;">阶段三</td>
<td class="bot_2" style="width: 88px;" align="center">UNICODE<br />
（国际化）</td>
<td class="bot_2">为了使国际间信息交流更加方便，国际组织制定了<strong>UNICODE 字符集</strong>，为各种语言中的每一个字符设定了统一并且唯一的数字编号，以满足跨语言、跨平台进行文本转换、处理的要求。</td>
<td class="bot_2">Windows NT/2000/XP，Linux，Java</td>
</tr>
</tbody>
</table>
<p>字符串在内存中的存放方法：</p>
<p>在 ASCII 阶段，<strong>单字节字符串</strong>使用一个字节存放一个字符（SBCS）。比如，"Bob123" 在内存中为：</p>
<table style="color: #000080; 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="line-height: 1%; margin: 0px; font-size: 1px; border-width: 0px; padding: 0px;">
<td bgcolor="#000080"></td>
<td bgcolor="#000080"></td>
<td bgcolor="#000080"></td>
<td bgcolor="#000080"></td>
<td bgcolor="#000080"></td>
<td bgcolor="#000080"></td>
<td bgcolor="#000080"></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），因此，这种方式存放的字符也被称作<strong>多字节字符</strong>。比如，"中文123" 在中文 Windows 95 内存中为7个字节，每个汉字占2个字节，每个英文和数字字符占1个字节：</p>
<table style="color: #000080; 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="line-height: 1%; margin: 0px; font-size: 1px; border-width: 0px; padding: 0px;">
<td colspan="2" bgcolor="#ff0000"></td>
<td colspan="2" bgcolor="#ff0000"></td>
<td bgcolor="#000080"></td>
<td style="background-color: #000080;"></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），因此，这种方式存放的字符也被称作<strong>宽字节字符</strong>。比如，字符串 "中文123" 在 Windows 2000 下，内存中实际存放的是 5 个序号：</p>
<table style="color: #000080; 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><span style="color: #808080;"> ← 在 x86 CPU 中，低字节在前</span></td>
</tr>
<tr style="line-height: 1%; margin: 0px; font-size: 1px; border-width: 0px; padding: 0px;">
<td colspan="2" bgcolor="#ff0000"></td>
<td colspan="2" bgcolor="#ff0000"></td>
<td colspan="2" bgcolor="#ff0000"></td>
<td style="background-color: #ff0000;" colspan="2"></td>
<td colspan="2" bgcolor="#ff0000"></td>
<td colspan="2" bgcolor="#ff0000"></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"></td>
<td class="top_2" style="width: 600px;" align="center"><strong>概念描述</strong></td>
<td class="top_2" style="width: 257px;" align="center"><strong>举例</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td class="con_1" align="center">字符</td>
<td class="con_2" style="width: 600px;">人们使用的记号，抽象意义上的一个符号。</td>
<td class="con_2" style="width: 257px;">'1', '中', 'a', '$', '￥', ……</td>
</tr>
<tr>
<td class="con_1" align="center">字节</td>
<td class="con_2" style="width: 600px;">计算机中存储数据的单元，一个8位的二进制数，是一个很具体的存储空间。</td>
<td class="con_2" style="width: 257px;">0x01, 0x45, 0xFA, ……</td>
</tr>
<tr>
<td class="con_1" align="center">ANSI<br />
字符串</td>
<td class="con_2" style="width: 600px;">在内存中，如果“字符”是以 <strong>ANSI 编码</strong>形式存在的，一个字符可能使用一个字节或多个字节来表示，那么我们称这种字符串为 <strong>ANSI 字符串</strong>或者<strong>多字节字符串</strong>。</td>
<td class="con_2" style="width: 257px;">"中文123"<br />
<span class="rem">（占7字节）</span></td>
</tr>
<tr>
<td class="bot_1" align="center">UNICODE<br />
字符串</td>
<td class="bot_2" style="width: 600px;">在内存中，如果“字符”是以在 UNICODE 中的序号存在的，那么我们称这种字符串为 <strong>UNICODE 字符串</strong>或者<strong>宽字节字符串</strong>。</td>
<td class="bot_2" style="width: 257px;">L"中文123"<br />
<span class="rem">（占10字节）</span></td>
</tr>
</tbody>
</table>
<p>由于不同 ANSI 编码所规定的标准是不相同的，因此，对于一个给定的<strong>多字节字符串</strong>，我们必须知道它采用的是哪一种编码规则，才能够知道它包含了哪些“字符”。而对于 <strong>UNICODE 字符串</strong>来说，不管在什么环境下，它所代表的“字符”内容总是不变的。</p>
<h3>字符集与编码</h3>
<p>各个国家和地区所制定的不同 ANSI 编码标准中，都只规定了各自语言所需的“字符”。比如：汉字标准（GB2312）中没有规定韩国语字符怎样存储。这些 ANSI 编码标准所规定的内容包含两层含义：</p>
<ol>
<li>使用哪些字符。也就是说哪些汉字，字母和符号会被收入标准中。所包含“字符”的集合就叫做“<strong>字符集</strong>”。</li>
<li>规定每个“字符”分别用一个字节还是多个字节存储，用哪些字节来存储，这个规定就叫做“<strong>编码</strong>”。</li>
</ol>
<p>各个国家和地区在制定编码标准的时候，“字符的集合”和“编码”一般都是同时制定的。因此，平常我们所说的“字符集”，比如：GB2312, GBK, JIS 等，除了有“字符的集合”这层含义外，同时也包含了“编码”的含义。</p>
<p>“<strong>UNICODE 字符集</strong>”包含了各种语言中使用到的所有“字符”。用来给 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"><strong>分类</strong></td>
<td class="top_2" style="width: 83px;" align="center"><strong>编码标准</strong></td>
<td class="top_2" style="width: 738px;" align="center"><strong>说明</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td class="con_1" align="center">单字节字符编码</td>
<td class="con_2" style="width: 83px;">ISO-8859-1</td>
<td class="con_2" style="width: 738px;">最简单的编码规则，每一个字节直接作为一个 UNICODE 字符。比如，[0xD6, 0xD0] 这两个字节，通过 iso-8859-1 转化为字符串时，将直接得到 [0x00D6, 0x00D0] 两个 UNICODE 字符，即 "?D"。</p>
<p>反之，将 UNICODE 字符串通过 iso-8859-1 转化为字节串时，只能正常转化 0~255 范围的字符。</td>
</tr>
<tr>
<td class="con_1" align="center">ANSI 编码</td>
<td class="con_2" style="width: 83px;">GB2312,<br />
BIG5,<br />
Shift_JIS,<br />
ISO-8859-2 ……</td>
<td class="con_2" style="width: 738px;">把 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" align="center">UNICODE 编码</td>
<td class="bot_2" style="width: 83px;">UTF-8,<br />
UTF-16, UnicodeBig ……</td>
<td class="bot_2" style="width: 738px;">与“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"><strong>类型或操作</strong></td>
<td class="top_2" align="center"><strong>C++</strong></td>
<td class="top_2" align="center"><strong>Java</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td class="con_1" align="center">字符</td>
<td class="con_2">wchar_t</td>
<td class="con_2">char</td>
</tr>
<tr>
<td class="con_1" align="center">字节</td>
<td class="con_2">char</td>
<td class="con_2">byte</td>
</tr>
<tr>
<td class="con_1" align="center">ANSI 字符串</td>
<td class="con_2">char[]</td>
<td class="con_2">byte[]</td>
</tr>
<tr>
<td class="con_1" align="center">UNICODE 字符串</td>
<td class="con_2">wchar_t[]</td>
<td class="con_2">String</td>
</tr>
<tr>
<td class="con_1" align="center">字节串→字符串</td>
<td class="con_2">mbstowcs(), MultiByteToWideChar()</td>
<td class="con_2">string = new String(bytes, "encoding")</td>
</tr>
<tr>
<td class="bot_1" align="center">字符串→字节串</td>
<td class="bot_2">wcstombs(), WideCharToMultiByte()</td>
<td class="bot_2">bytes = string.getBytes("encoding")</td>
</tr>
</tbody>
</table>
<p>以上需要注意几点：</p>
<ol>
<li>Java 中的 char 代表一个“UNICODE 字符（宽字节字符）”，而 C++ 中的 char 代表一个字节。</li>
<li>MultiByteToWideChar() 和 WideCharToMultiByte() 是 Windows API 函数。</li>
</ol>
<h3>C++ 中相关实现方法</h3>
<p>声明一段字符串常量：</p>
<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> sz[<span class="number">20</span>] = <span class="string">"中文123"</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">"\x4E2D\x6587\x0031\x0032\x0033"</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">".936"</span>);</p>
<p><span class="rem">// GCC 中格式</span><br />
setlocale(LC_ALL, <span class="string">"zh_CN.GBK"</span>);</p>
<p><span class="rem">// Visual C++ 中使用小写 %s，按照 setlocale 指定编码输出到文件<br />
// GCC 中使用大写 %S</span><br />
fwprintf(fp, L<span class="string">"%s\n"</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">".936"</span>)</p>
<p><span class="rem">// UNICODE 字符串常量，内容长度 10 字节</span><br />
wchar_t wsz[<span class="number">20</span>] = L<span class="string">"中文 123"</span>;</td>
</tr>
</tbody>
</table>
<p>以上需要注意 #pragma setlocale 与 setlocale(LC_ALL, "") 的作用是不同的，#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">"中文123"</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 class="key"><br />
byte</span> [] bytes = string.getBytes(<span class="string">"GB2312"</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">"GB2312"</span>);</p>
<p><span class="rem">// 要将 String 按照某种编码写入文本文件，有两种方法： </span></p>
<p>// 第一种办法：用 Stream 类写入已经按照指定编码转化好的字节串<br />
OutputStream os = <span class="key">new</span> FileOutputStream(<span class="string">"1.txt"</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">"2.txt"</span>), <span class="string">"GB2312"</span>);<br />
ow.write(string);<br />
ow.close();</p>
<p><span class="rem">/* 最后得到的 1.txt 和 2.txt 都是 7 个字节 */</span></p>
<p></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 <span style="color: #ff0000;">-encoding BIG5</span> 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"></td>
<td class="top_2" align="center"><strong>对编码的误解</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td class="con_1" align="center">误解一</td>
<td class="con_2">在将“字节串”转化成“UNICODE 字符串”时，比如在读取文本文件时，或者通过网络传输文本时，容易将“字节串”简单地作为<strong>单字节字符串</strong>，采用每“一个字节”就是“一个字符”的方法进行转化。</p>
<p>而实际上，在非英文的环境中，应该将“字节串”作为 ANSI 字符串，采用适当的编码来得到 UNICODE 字符串，有可能“多个字节”才能得到“一个字符”。</p>
<p>通常，一直在英文环境下做开发的程序员们，容易有这种误解。</td>
</tr>
<tr>
<td class="bot_1" 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("iso-8859-1") 来进行逆向操作，得到原始的“字节串”。然后再使用正确的 ANSI 编码，比如 string = new String(bytes, "GB2312")，来得到正确的“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>当页面中的表单提交字符串时，首先把字符串按照当前页面的编码，转化成字节串。然后再将每个字节转化成 "%XX" 的格式提交到 Web 服务器。比如，一个编码为 GB2312 的页面，提交 "中" 这个字符串时，提交给服务器的内容为 "%D6%D0"。</p>
<p>在服务器端，Web 服务器把收到的 "%D6%D0" 转化成 [0xD6, 0xD0] 两个字节，然后再根据 GB2312 编码规则得到 "中" 字。</p>
<p>在 Tomcat 服务器中，request.getParameter() 得到乱码时，常常是因为前面提到的“误解一”造成的。默认情况下，当提交 "%D6%D0" 给 Tomcat 服务器时，request.getParameter() 将返回 [0x00D6, 0x00D0] 两个 UNICODE 字符，而不是返回一个 "中" 字符。因此，我们需要使用 bytes = string.getBytes("iso-8859-1") 得到原始的字节串，再用 string = new String(bytes, "GB2312") 重新得到正确的字符串 "中"。</p>
<h3>从数据库读取字符串</h3>
<p>通过数据库客户端（比如 ODBC 或 JDBC）从数据库服务器中读取字符串时，客户端需要从服务器获知所使用的 ANSI 编码。当数据库服务器发送字节流给客户端时，客户端负责将字节流按照正确的编码转化成 UNICODE 字符串。</p>
<p>如果从数据库读取字符串时得到乱码，而数据库中存放的数据又是正确的，那么往往还是因为前面提到的“误解一”造成的。解决的办法还是通过 string = new String( string.getBytes("iso-8859-1"), "GB2312") 的方法，重新得到原始的字节串，再重新使用正确的编码转化成字符串。</p>
<h3>电子邮件中的字符串</h3>
<p>当一段 Text 或者 HTML 通过电子邮件传送时，发送的内容首先通过一种指定的<strong>字符编码</strong>转化成“字节串”，然后再把“字节串”通过一种指定的<strong>传输编码</strong>（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 />
<span style="color: #ff0000;">charset="gb2312"</span><br />
<span style="color: #ff0000;">Content-Transfer-Encoding: base64</span></p>
<p>sbG+qcrQuqO17cf4yee74bGjz9W7+b3wudzA7dbQ0MQNCg0KvPKzxqO6uqO17cnnsaPW0NDEDQoNCg==</td>
</tr>
</tbody>
</table>
<p>最常用的 Content-Transfer-Encoding 有 Base64 和 Quoted-Printable 两种。在对二进制文件或者中文文本进行转化时，Base64 得到的“字节串”比 Quoted-Printable 更短。在对英文文本进行转化时，Quoted-Printable 得到的“字节串”比 Base64 更短。</p>
<p>邮件的标题，用了一种更简短的格式来标注“字符编码”和“传输编码”。比如，标题内容为 "中"，则在邮件源代码中表示为：</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: #ffff00;">=?</span>GB2312<span style="background-color: #ffff00;">?B?</span>1tA=<span style="background-color: #ffff00;">?=</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，同样，如果标题内容为 "中"：</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: #ffff00;">=?</span>GB2312<span style="background-color: #ffff00;">?Q?</span>=D6=D0<span style="background-color: #ffff00;">?=</span></td>
</tr>
</tbody>
</table>
<p>如果阅读邮件时出现乱码，一般是因为“字符编码”或“传输编码”指定有误，或者是没有指定。比如，有的发邮件组件在发送邮件时，标题 "中"：</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: #ffff00;">=?</span><span style="color: #ff0000;">ISO-8859-1</span><span style="background-color: #ffff00;">?Q?</span>=D6=D0<span style="background-color: #ffff00;">?=</span></td>
</tr>
</tbody>
</table>
<p>这样的表示，实际上是明确指明了标题为 [0x00D6, 0x00D0]，即 "?D"，而不是 "中"。</p>
<h2>几种错误理解的纠正</h2>
<h5>误解：“ISO-8859-1 是国际编码？”</h5>
<p>非也。iso-8859-1 只是单字节字符集中最简单的一种，也就是“字节编号”与“UNICODE 字符编号”一致的那种编码规则。当我们要把一个“字节串”转化成“字符串”，而又不知道它是哪一种 ANSI 编码时，先暂时地把“每一个字节”作为“一个字符”进行转化，不会造成信息丢失。然后再使用 bytes = string.getBytes("iso-8859-1") 的方法可恢复到原始的字节串。</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> </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="关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312&#8230;） (2010/06/18)">关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312&#8230;）</a> </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> </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> </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> </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> </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>Asp.Net页面的编码问题</title>
		<link>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</link>
		<comments>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#comments</comments>
		<pubDate>Fri, 27 Nov 2009 06:13:00 +0000</pubDate>
		<dc:creator>Kevin Yang</dc:creator>
				<category><![CDATA[疑难杂症]]></category>
		<category><![CDATA[AspDotNet]]></category>
		<category><![CDATA[Content-Type]]></category>
		<category><![CDATA[HTTP Watch]]></category>
		<category><![CDATA[Response]]></category>
		<category><![CDATA[字符集]]></category>
		<category><![CDATA[样式表]]></category>
		<category><![CDATA[编解码]]></category>

		<guid isPermaLink="false">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</guid>
		<description><![CDATA[莫名其妙的问题
<p>这些天，一个客户的网站遇到了一些莫名其妙的问题。先是首页在Firefox下看正常，但是在IE上看，同个样式表文件，却有部分样式没能加载，导致导航菜单没有背景并且走位了。后来样式的问题解决了，但是又出现了一个页面上，部分文字乱码部分正常的诡异问题。</p>
字符编码导致的样式文件解析错误
<p>对于第一个&#8230;</p>]]></description>
			<content:encoded><![CDATA[<h2>莫名其妙的问题</h2>
<p>这些天，一个客户的网站遇到了一些莫名其妙的问题。先是首页在Firefox下看正常，但是在IE上看，同个样式表文件，却有部分样式没能加载，导致导航菜单没有背景并且走位了。后来样式的问题解决了，但是又出现了一个页面上，部分文字乱码部分正常的诡异问题。</p>
<h2>字符编码导致的样式文件解析错误</h2>
<p>对于第一个问题我的第一反应是CSS样式表中部分样式存在不兼容性。但是检查了导航菜单样式的写法，没有看出来有什么特别的，都是常见的属性常见的值。但是用IE自带的开发者工具看了一下出问题的元素的样式，发现和在Firebug中看到的样式居然不同。后来无意中在HTTP Watch中看了style.css文件的内容，发现注释有乱码，当即联想到以前写过的文章《<a title="文档字符集导致的脚本错误" 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" target="_blank">文档字符集导致的脚本错误</a>》。当一个文档（HTML文档或是CSS文档）的文件存储字符集和浏览器解码使用的字符集不一致的时候会出现类似的问题，特别是utf-8编码过的文档却以gb2312去解码的时候，出现的问题就更诡异了。</p>
<p>于是我在IE上改用UTF-8去解析网页，结果果然，导航菜单的样式正常了，背景图片也正常加载了（虽然其他地方出现了一些乱码情况）。这证明了我的思路是对的。但是具体是哪个地方出了问题呢，为什么Firefox、Opera和Chrome下都没问题，偏偏就IE有问题呢？</p>
<p>我又仔细查看了客户给出的网页和样式文件，又分析了相关的HTTP消息，发现请求.aspx文件（出问题的网页文档）的时候，服务端返回的Content-Type是text/html;charset=gb2312，那么所有浏览器自然会以服务器返回的字符集来解析文档，这个没问题，但是<strong><font color="#008000">当网页中引用了外部资源的时候（在这里是css样式表），IE是使用当前网页的文档字符集去解析此外部资源，除非外部资源声明了编码格式（HTTP响应头中声明或是在文档最前方使用@charset声明），而Firefox、Opera和Chrome在CSS文件没有显式声明字符集的情况下，始终是以UTF-8来解码的。</font></strong></p>
<p>问题既然和样式相关，也就是说在使用gb2312解析该样式表的时候出现了问题，那可以猜到，样式表文件实际的存储格式是utf-8的。</p>
<p>为了找到具体出问题的地方，我用Notepad++——貌似手头只有这软件可以自由编解码——模拟了一下使用gb2312解码此utf-8格式的样式表的场景，发现了其中一段出问题的样式，UTF-8解码后很正常，因为本身就是UTF-8编码的：</p>
<pre class="csharpcode"><span class="rem">/*新导航*/</span>
#new_nav ul{ width:672px; height:33px; margin:0px; padding:0px;  }</pre>
<p>当使用gb2312解码之后得到如下:</p>
<pre class="csharpcode">/*鏂板鑸?/
#new_nav ul{ width:672px; height:33px; margin:0px; padding:0px;}</pre>
<p>很明显的，下面的样式都被注释掉了。自然一切问题都得以解释了。</p>
<p>解决办法很多种，但目标都是一个，保证文档声明和实际字符集的一致。对于CSS样式表，我们可以在文件头显式设置@charset ‘XXX’来声明字符集。或者在使用link引用外部样式表的时候显式加上charset属性。</p>
<p>另外，如果所有文件都能保存为utf-8 with signature的话，也就是带BOM标记的UTF-8编码格式，那么所有浏览器都能自识别，而不会产生识别错误的问题。</p>
<h2>Asp.Net中涉及到的编码问题</h2>
<p>第二个问题虽然我知道还是编解码的环节出了问题，但还是分析了我半天才找到问题所在。</p>
<h3>测试环境简化</h3>
<p>为了简单，我把第二个问题中涉及到的页面简化成如下两个文件：</p>
<p>Default.aspx文件：</p>
<pre class="csharpcode"><span class="asp">&lt;%@ Page Language=&quot;C#&quot; AutoEventWireup=&quot;true&quot; CodeFile=&quot;Default.aspx.cs&quot; Inherits=&quot;_Default&quot; %&gt;</span>
<span class="kwrd">&lt;!</span><span class="html">DOCTYPE</span> <span class="attr">html</span> <span class="attr">PUBLIC</span> <span class="kwrd">&quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot;</span> <span class="kwrd">&quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;</span><span class="html">html</span> <span class="attr">xmlns</span><span class="kwrd">=&quot;http://www.w3.org/1999/xhtml&quot;</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;</span><span class="html">head</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;/</span><span class="html">head</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;</span><span class="html">body</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">p</span><span class="kwrd">&gt;</span><span class="asp">&lt;%</span>= Msg <span class="asp">%&gt;</span><span class="kwrd">&lt;/</span><span class="html">p</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">p</span><span class="kwrd">&gt;</span>
        Aspx页面中的中文
    <span class="kwrd">&lt;/</span><span class="html">p</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;/</span><span class="html">body</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;/</span><span class="html">html</span><span class="kwrd">&gt;</span></pre>
<p>Default.aspx.cs后台代码文件：</p>
<pre class="csharpcode"><span class="kwrd">using</span> System;
<span class="kwrd">public</span> <span class="kwrd">partial</span> <span class="kwrd">class</span> _Default : System.Web.UI.Page
{
    <span class="kwrd">public</span> String Msg = <span class="str">&quot;代码中的中文&quot;</span>;
    <span class="kwrd">protected</span> <span class="kwrd">void</span> Page_Load(<span class="kwrd">object</span> sender, EventArgs e)
    {
    }
}</pre>
<p>两个文件非常简单，从aspx文件中输出一段文字，从cs文件中输出一段文件。</p>
<h3>Asp.Net页面涉及到的编解码环节</h3>
<p>为了理解第二个问题，我们首先先来看一下在Asp.Net中一个页面究竟会涉及到什么编码环节。</p>
<p><font color="#800080">1. 文件本身存储使用的编码（在Visual Studio中新建文件的默认编码是UTF-8 With BOM）</font></p>
<p><font color="#800080">2. 文件被Asp.Net引擎转换处理时使用的编码</font></p>
<p><font color="#800000">3. 文件写到HTTP响应流时使用的编码</font></p>
<p><font color="#800000">4. 文件在HTTP头声明的编码（Content-Type=”text/html;charset=xxx”）</font></p>
<p>对于第一个环节就不需要说了，直接“Advanced Save As..”然后选择相应编码即可。对于2、3、4环节中涉及到的编码，asp.Net提供了全局配置还有页面级配置两种方式。</p>
<p>在web.config文件中可以管理全局编码配置。</p>
<pre class="csharpcode"><span class="kwrd">&lt;</span><span class="html">configuration</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">system.web</span><span class="kwrd">&gt;</span>
        <span class="kwrd">&lt;</span><span class="html">globalization</span> <span class="attr">responseEncoding</span><span class="kwrd">=&quot;utf-8&quot;</span> <span class="attr">fileEncoding</span><span class="kwrd">=&quot;utf-8&quot;</span> <span class="kwrd">/&gt;</span>
    <span class="kwrd">&lt;/</span><span class="html">system.web</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;/</span><span class="html">configuration</span><span class="kwrd">&gt;</span></pre>
<p>在system.web一节下添加globalization元素，它有几个可配置选项，具体参见<a title="Configures the globalization settings for an application" href="http://msdn.microsoft.com/en-us/library/hy4kkhe0.aspx" target="_blank">MSDN</a>。其中有两个属性和此次的问题相关，一个是fileEncoding（注意区分大小写），这个配置项决定了asp.net引擎在将aspx文件以及cs文件合并成html文档时使用的编码。<strong><font color="#008000">如果aspx或者cs文件的格式和此属性配置的不一致，那么就有可能导致生成的html文档出现乱码</font></strong>。默认情况下，该属性的值为ANSI编码，也就是说中文系统下为GB2312。但是如果文档本身存储格式是UTF-8 with BOM，那么asp.net将始终以UTF-8编码来解析aspx和cs文件。这也是为什么默认情况下什么都不需要配置的缘故。</p>
<p>另外一个属性是responseEncoding，这个配置决定了上述的第3、4环节，也就是当asp.net生成好html文档之后，将其写入HTTP响应正文时使用的编码，同时会设置HTTP的Content-Type响应头中的charset域。如果前面的环节没有问题，html生成正常的话，那么这里无论设置什么编码浏览器均会正常解码，页面上都不会有乱码。但是如果前面的步骤出了问题，那自然就会有乱码产生了。</p>
<p>注意上面4个环节我分别用了两种颜色来标明，看了上面的解释之后你就很明白了，1和2环节编码需一致，否则中间生成的html文档就已经有乱码了。3和4环节的编码须一致，否则浏览器显示不正常。</p>
<p>客户网站出现的部分文字乱码问题的原因就是因为aspx文件存储的格式是不带BOM标记的UTF-8（天知道他是怎么给整成这个编码的）而CS文件没有改动过，还是UTF-8 with BOM，而全局的fileEncoding没有配置，因此在第二个环节的时候使用gb2312去解析一个utf-8的文档，自然有问题，因此aspx文件中的中文显示为乱码，而cs文件中指定的中文正常显示。</p>
<p>上面介绍的是asp.net中的全局编码配置，其实针对每个页面还可以进行特殊配置。例如在aspx页面中的Page指令中可以添加ResponseEncoding属性。</p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://www.imkevinyang.com/wp-content/uploads/2009/11/image_thumb22.png" width="244" height="36" /> </p>
<p>或者我们可以在后台C#代码中利用Response对象。</p>
<p>Response对象提供了两个和字符集编码相关的属性，Response.Charset和Response.ContentEncoding。这两个有什么区别呢？ContentEncoding和web.config中配置的responseEncoding是等价的，都是既影响HTTP响应流编码又影响Content-Type响应头，而Charset影响的只是响应头Content-Type而已。 将其设置为null的话，Content-Type中就不会有charset域了。</p>
<p align="right">——<a href="http://www.imkevinyang.com/"><em><strong>Kevin Yang</strong></em></a></p>

	标签：<a href="http://www.imkevinyang.com/tags/aspdotnet" title="AspDotNet" rel="tag">AspDotNet</a>, <a href="http://www.imkevinyang.com/tags/content-type" title="Content-Type" rel="tag">Content-Type</a>, <a href="http://www.imkevinyang.com/tags/http-watch" title="HTTP Watch" rel="tag">HTTP Watch</a>, <a href="http://www.imkevinyang.com/tags/response" title="Response" rel="tag">Response</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/%e6%a0%b7%e5%bc%8f%e8%a1%a8" title="样式表" rel="tag">样式表</a>, <a href="http://www.imkevinyang.com/categories/techarticles/knottyproblems" 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/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> </li>
	<li><a href="http://www.imkevinyang.com/2010/02/silverlight%e5%90%af%e7%94%a8assembly-caching%e4%b9%8b%e5%90%8e%e9%93%81%e9%80%9a%e7%94%a8%e6%88%b7%e6%97%a0%e6%b3%95%e8%ae%bf%e9%97%ae.html" title="Silverlight启用Assembly Caching之后铁通用户无法访问 (2010/02/13)">Silverlight启用Assembly Caching之后铁通用户无法访问</a> </li>
	<li><a href="http://www.imkevinyang.com/2009/11/%e3%80%90%e6%8e%a8%e8%8d%90%e3%80%91%e4%b8%a4%e6%ac%behttp%e6%b5%81%e9%87%8f%e5%88%86%e6%9e%90%e5%b7%a5%e5%85%b7%e7%9a%84%e6%af%94%e8%be%83.html" title="【推荐】两款HTTP流量分析工具的比较 (2009/11/08)">【推荐】两款HTTP流量分析工具的比较</a> </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="关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312&#8230;） (2010/06/18)">关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312&#8230;）</a> </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> </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> </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> </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> </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> </li>
	<li><a href="http://www.imkevinyang.com/2009/07/%ef%bc%88%e8%bd%ac%ef%bc%89http-%e8%af%b7%e6%b1%82%e5%a4%84%e7%90%86%e6%b5%81%e7%a8%8b.html" title="（转）Http 请求处理流程 (2009/07/20)">（转）Http 请求处理流程</a> </li>
</ul>

]]></content:encoded>
			<wfw:commentRss>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/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>解决Xaml中包含中文导致无法编译的问题</title>
		<link>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</link>
		<comments>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#comments</comments>
		<pubDate>Tue, 29 Sep 2009 04:41:00 +0000</pubDate>
		<dc:creator>Kevin Yang</dc:creator>
				<category><![CDATA[Silverlight]]></category>
		<category><![CDATA[Xaml]]></category>
		<category><![CDATA[Xml]]></category>
		<category><![CDATA[中文]]></category>
		<category><![CDATA[字符集]]></category>
		<category><![CDATA[编译错误]]></category>

		<guid isPermaLink="false">http://www.imkevinyang.com/2009/10/%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</guid>
		<description><![CDATA[<p>最近同事在编译工程的时候发现一个奇怪的Xml验证错误，提示信息是The "ValidateXaml" task failed unexpectedly。检查了半天发现是Generic.xaml文件中的中文字符导致的。只要文件中包含中文，那么就会出现这个编译错误。即使注释掉也依然出错。</p>
<p>这就奇怪了，按说Xaml&#8230;</p>]]></description>
			<content:encoded><![CDATA[<p>最近同事在编译工程的时候发现一个奇怪的Xml验证错误，提示信息是The "ValidateXaml" task failed unexpectedly。检查了半天发现是Generic.xaml文件中的中文字符导致的。只要文件中包含中文，那么就会出现这个编译错误。即使注释掉也依然出错。</p>
<p>这就奇怪了，按说Xaml中是可以直接写中文的。想了一下，这个情况和我之前遇到的脚本字符集的问题类似（参考：<a title="文档字符集导致的脚本错误" 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" target="_blank">文档字符集导致的脚本错误</a>）。我查看了一下这个Generic.xaml文件本身的编码，发现是GB2312，重新存成UTF-8之后，成功编译，问题解决。</p>
<p>问题的原因就在于，<strong>文件本身使用的编码和它声明的编码不一致。</strong>xml解析器（也就是silverlight的xaml解析器）在对字节流（该字节流之前是使用gb2312进行编码的）进行解码时，用的就是声明的编码（如果没有指定，则默认为utf-8），因此这种不一致的问题会导致解码之后得到一些意想不到的字符，导致silverlight的xaml解析器无法识别，从而抛出上述的异常。例如一个本应该解析成闭合符&gt;的，结果被解析成其他字符。</p>
<p>知道这个原因之后我们要做的就是，保证文件存储的编码格式和它声明的编码格式一致即可。</p>
<p>A方案：将文件另存为UTF-8编码格式。</p>
<p>B方案：显式指定Xaml文件的字符集声明。由于Xaml也是xml，因此我们可以在文件中加上xml声明头，如下：</p>
<pre class="csharpcode"><span class="kwrd">&lt;?</span><span class="html">xml</span> <span class="attr">version</span><span class="kwrd">="1.0"</span> <span class="attr">encoding</span><span class="kwrd">="gb2312"</span> ?<span class="kwrd">&gt;</span>
<span class="kwrd">&lt;</span><span class="html">ResourceDictionary</span>
    <span class="attr">xmlns</span><span class="kwrd">="http://schemas.microsoft.com/winfx/2006/xaml/presentation"</span>
    <span class="attr">xmlns:x</span><span class="kwrd">="http://schemas.microsoft.com/winfx/2006/xaml"</span><span class="kwrd">&gt;</span>
    <span class="rem">&lt;!--测试--&gt;</span>
<span class="kwrd">&lt;/</span><span class="html">ResourceDictionary</span><span class="kwrd">&gt;</span></pre>
<p>不过建议使用A方案，这样兼容性更好一些。</p>
<p style="text-align: right;">——<a title="解决Xaml中包含中文导致无法编译的问题" 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" target="_self"><em><strong>Kevin Yang</strong></em></a></p>

	标签：<a href="http://www.imkevinyang.com/categories/techarticles/silverlight" title="Silverlight" rel="tag">Silverlight</a>, <a href="http://www.imkevinyang.com/tags/xaml" title="Xaml" rel="tag">Xaml</a>, <a href="http://www.imkevinyang.com/tags/xml" title="Xml" rel="tag">Xml</a>, <a href="http://www.imkevinyang.com/tags/%e4%b8%ad%e6%96%87" 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%af%91%e9%94%99%e8%af%af" 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> </li>
	<li><a href="http://www.imkevinyang.com/2009/03/silverlight-2%e5%8a%a8%e6%80%81%e5%8a%a0%e8%bd%bdxap%e5%8c%85%e4%b8%ad%e7%9a%84%e7%a8%8b%e5%ba%8f%e9%9b%86.html" title="Silverlight 2动态加载Xap包中的程序集 (2009/03/02)">Silverlight 2动态加载Xap包中的程序集</a> </li>
	<li><a href="http://www.imkevinyang.com/2010/09/%e4%bd%bf%e7%94%a8xamlreader-load%e6%9e%84%e5%bb%ba%e9%85%8d%e7%bd%ae%e5%9e%8b%e8%87%aa%e5%ae%9a%e4%b9%89%e6%8e%a7%e4%bb%b6.html" title="使用XamlReader.Load构建配置型自定义控件 (2010/09/03)">使用XamlReader.Load构建配置型自定义控件</a> </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="关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312&#8230;） (2010/06/18)">关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312&#8230;）</a> </li>
	<li><a href="http://www.imkevinyang.com/2009/02/%e5%9c%a8xaml%e4%b8%ad%e8%be%93%e5%85%a5%e5%b0%96%e6%8b%ac%e5%8f%b7%e6%96%87%e6%9c%ac.html" title="在Xaml中输入尖括号文本 (2009/02/28)">在Xaml中输入尖括号文本</a> </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> </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> </li>
	<li><a href="http://www.imkevinyang.com/2010/05/%e5%bd%93google-analytics%e3%80%81firefox%e5%92%8ciis%e8%b5%b0%e5%88%b0%e4%ba%86%e4%b8%80%e8%b5%b7.html" title="当Google Analytics、Firefox和IIS走到了一起&#8230; (2010/05/29)">当Google Analytics、Firefox和IIS走到了一起&#8230;</a> </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> </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> </li>
</ul>

]]></content:encoded>
			<wfw:commentRss>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/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>文档字符集导致的脚本错误</title>
		<link>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</link>
		<comments>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#comments</comments>
		<pubDate>Tue, 18 Aug 2009 18:01:25 +0000</pubDate>
		<dc:creator>Kevin Yang</dc:creator>
				<category><![CDATA[Web传统技术]]></category>
		<category><![CDATA[charset]]></category>
		<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/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</guid>
		<description><![CDATA[<p>Html页面中可以通过meta标签指定页面文档使用的字符集，这样浏览器就会根据此标签使用指定的字符集去解析文档流，而不用靠“猜”了。</p>
<pre class="csharpcode"><span class="kwrd">&#60;</span><span class="html">meta</span> <span class="attr">http-equiv</span><span class="kwrd">=&#34;Content-Type&#34;</span> <span class="attr">content</span><span class="kwrd">=&#34;text/html; charset=utf-8&#038;quo&#8230;</span></pre>]]></description>
			<content:encoded><![CDATA[<p>Html页面中可以通过meta标签指定页面文档使用的字符集，这样浏览器就会根据此标签使用指定的字符集去解析文档流，而不用靠“猜”了。</p>
<pre class="csharpcode"><span class="kwrd">&lt;</span><span class="html">meta</span> <span class="attr">http-equiv</span><span class="kwrd">=&quot;Content-Type&quot;</span> <span class="attr">content</span><span class="kwrd">=&quot;text/html; charset=utf-8&quot;</span> <span class="kwrd">/&gt;</span></pre>
<p>文档字符集需要对应文件实际存储使用的字符集，否则会引发很多意料不到而又难以调试的Bug。</p>
<h2>问题1——页面显示乱码</h2>
<p>如果文档中指定的字符集和文件实际存储的字符集不一致，那么就会造成页面的非ASCII字符显示为乱码。如下面的HTML页面（文件的实际存储格式为UTF-8）：</p>
<pre class="csharpcode">

<span class="kwrd">&lt;!</span><span class="html">DOCTYPE</span> <span class="attr">html</span> <span class="attr">PUBLIC</span> <span class="kwrd">&quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot;</span> <span class="kwrd">&quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;</span><span class="html">html</span> <span class="attr">xmlns</span><span class="kwrd">=&quot;http://www.w3.org/1999/xhtml&quot;</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">head</span><span class="kwrd">&gt;</span>
        <span class="kwrd">&lt;</span><span class="html">meta</span> <span class="attr">http-equiv</span><span class="kwrd">=&quot;Content-Type&quot;</span> <span class="attr">content</span><span class="kwrd">=&quot;text/html; charset=gb2312&quot;</span> <span class="kwrd">/&gt;</span>
    <span class="kwrd">&lt;/</span><span class="html">head</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">body</span><span class="kwrd">&gt;</span>
        <span class="kwrd">&lt;</span><span class="html">div</span><span class="kwrd">&gt;</span>中文<span class="kwrd">&lt;/</span><span class="html">div</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;/</span><span class="html">body</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;/</span><span class="html">html</span><span class="kwrd">&gt;</span>

<span class="kwrd"></span>&#160;
</pre>
<p>“中文”会变成“涓枃”。</p>
<h2>问题2——脚本执行结果不正确</h2>
<p>问题1实际上还是比较容易发现的，因为一看到乱码就可以猜到和字符集相关了。但是如果假设你在上面这个页面的基础上写了如下Javascript代码：</p>
<pre class="csharpcode"><span class="kwrd">&lt;</span><span class="html">script</span> <span class="attr">type</span><span class="kwrd">=&quot;text/javascript&quot;</span><span class="kwrd">&gt;</span>
    location.href = <span class="str">&quot;http://www.example.com/search?q=&quot;</span> + encodeURI(<span class="str">&quot;中文&quot;</span>);
<span class="kwrd">&lt;/</span><span class="html">script</span><span class="kwrd">&gt;</span></pre>
<p>你期望将用户重定向到一个特定的页面，你直接在页面中使用了Unicode字符串，并期望encodeURI会使用UTF-8对其进行Url编码。但是实际上你获得的Url却是：</p>
<p>http://www.example.com/search?q=%E6%B6%93%EE%85%9F%E6%9E%83</p>
<p>后台在解析这个Url参数的时候无论使用何种字符集进行Url解码，都无法得到“中文”这个字符串。原因和文章开头提到的乱码问题实际上是同一个问题。<strong><font color="#008000">如果你的文档字符集和页面存储字符集不一致，那么你在使用非ASCII码字符作字面量（就是直接写在代码里面的字符串）的时候就会出现意料不到的各种问题</font></strong>。</p>
<h2>问题3——脚本抛出异常</h2>
<p>在问题2举的例子中，脚本执行的时候没有抛出异常，但是其执行的结果却不是我们所期望的。在下面这个例子中，脚本的执行过程甚至会抛出莫名其妙的异常。</p>
<pre class="csharpcode"><span class="kwrd">&lt;</span><span class="html">script</span> <span class="attr">type</span><span class="kwrd">=&quot;text/javascript&quot;</span><span class="kwrd">&gt;</span>
    alert(<span class="str">&quot;对象初始化&quot;</span>);
<span class="kwrd">&lt;/</span><span class="html">script</span><span class="kwrd">&gt;</span></pre>
<p><img style="border-right-width: 0px; margin: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://www.imkevinyang.com/wp-content/uploads/2009/08/image_thumb9.png" width="565" height="93" /></p>
<p>如果你真到第8行去看那段脚本，你会发现，你死活就是看不出问题来，直到你把中文替换成英文之后你才可能会恍然大悟。</p>
<h2>问题4——引用外部脚本导致的异常</h2>
<p>现在我们把页面存储成gb2312，再运行问题3中的示例代码，这个时候就没有问题了。为了Html页面的纯洁性，我们将脚本移到一个独立的Js文件中去，并在Html中引用这个脚本文件。如下：</p>
<pre class="csharpcode">

页面index.html
<span class="kwrd">&lt;!</span><span class="html">DOCTYPE</span> <span class="attr">html</span> <span class="attr">PUBLIC</span> <span class="kwrd">&quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot;</span> <span class="kwrd">&quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;</span><span class="html">html</span> <span class="attr">xmlns</span><span class="kwrd">=&quot;http://www.w3.org/1999/xhtml&quot;</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">head</span><span class="kwrd">&gt;</span>
        <span class="kwrd">&lt;</span><span class="html">meta</span> <span class="attr">http-equiv</span><span class="kwrd">=&quot;Content-Type&quot;</span> <span class="attr">content</span><span class="kwrd">=&quot;text/html; charset=gb2312&quot;</span> <span class="kwrd">/&gt;</span>
    <span class="kwrd">&lt;/</span><span class="html">head</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">body</span><span class="kwrd">&gt;</span>
        <span class="kwrd">&lt;</span><span class="html">script</span> <span class="attr">type</span><span class="kwrd">=&quot;text/javascript&quot;</span> <span class="attr">src</span><span class="kwrd">=&quot;script/test.js&quot;</span><span class="kwrd">&gt;&lt;/</span><span class="html">script</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;/</span><span class="html">body</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;/</span><span class="html">html</span><span class="kwrd">&gt;</span>

脚本script/test.js
alert(&quot;对象初始化&quot;);

&#160;
</pre>
<p>这个时候再查看index.html页面的时候，我们发现，脚本又抛出问题3的异常来了。这是为什么呢？</p>
<p>这是因为，当Html页面在引用一个外部资源的时候，使用的字符集就是当前文档字符集。因此test.js文件会被浏览器使用gb2312字符集去解析，而我在Aptana中建此文件的时候，默认的文件存储格式就是utf-8，因此还是回归到了文档字符集和存储字符集不一致的问题上来。</p>
<p>这个问题，我们可以通过在引用外部资源的时候显式指定charset属性来解决。</p>
<pre class="csharpcode"><span class="kwrd">&lt;</span><span class="html">script</span> <span class="attr">type</span><span class="kwrd">=&quot;text/javascript&quot;</span> <span class="attr">src</span><span class="kwrd">=&quot;script/test.js&quot;</span> <span class="attr">charset</span><span class="kwrd">=&quot;utf-8&quot;</span><span class="kwrd">&gt;&lt;/</span><span class="html">script</span><span class="kwrd">&gt;</span></pre>
<p>&#160;</p>
<p><font color="#ff0000"><em><strong>update:</strong></em></font></p>
<p>以上试验提到的UTF-8均指UTF-8 without BOM，即不带标记的UTF-8。IE、Chrome、Opera和Safari均能支持BOM标记，这就意味着，如果你的文件本身是以UTF-8 with BOM编码的，那么无需指定charset，浏览器也能正确解析。此优先级最高。Firefox则无法正确识别BOM标记，因此总会以页面中指定的charset或者HTTP响应中的charset来解析文档。</p>
<p align="right">&#160;</p>
<p align="right">——<a href="http://www.imkevinyang.com/"><em><strong>Kevin Yang</strong></em></a></p>

	标签：<a href="http://www.imkevinyang.com/tags/charset" title="charset" rel="tag">charset</a>, <a href="http://www.imkevinyang.com/categories/techarticles/web%e4%bc%a0%e7%bb%9f%e6%8a%80%e6%9c%af" title="Web传统技术" rel="tag">Web传统技术</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%bc%95%e7%94%a8%e5%a4%96%e9%83%a8%e8%84%9a%e6%9c%ac" title="引用外部脚本" rel="tag">引用外部脚本</a>, <a href="http://www.imkevinyang.com/tags/%e6%96%87%e6%a1%a3%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>, <a href="http://www.imkevinyang.com/tags/%e8%84%9a%e6%9c%ac" title="脚本" rel="tag">脚本</a>, <a href="http://www.imkevinyang.com/tags/%e8%84%9a%e6%9c%ac%e5%bc%82%e5%b8%b8" 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> </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> </li>
	<li><a href="http://www.imkevinyang.com/2009/08/firebug%e5%9c%a8edithtml%e6%97%b6%e4%b8%a2%e5%a4%b1%e4%ba%8b%e4%bb%b6%e5%a4%84%e7%90%86%e5%87%bd%e6%95%b0-2.html" title="Firebug在EditHtml时丢失事件处理函数 (2009/08/19)">Firebug在EditHtml时丢失事件处理函数</a> </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="关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312&#8230;） (2010/06/18)">关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312&#8230;）</a> </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> </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> </li>
	<li><a href="http://www.imkevinyang.com/2010/02/%e5%bd%93%e5%89%8d%e6%97%a5%e6%9c%9f110%e5%b9%b4.html" title="当前日期110年 (2010/02/12)">当前日期110年</a> </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> </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> </li>
</ul>

]]></content:encoded>
			<wfw:commentRss>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/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>详解Javascript中的Url编码/解码</title>
		<link>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</link>
		<comments>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#comments</comments>
		<pubDate>Tue, 18 Aug 2009 17:21:35 +0000</pubDate>
		<dc:creator>Kevin Yang</dc:creator>
				<category><![CDATA[Web传统技术]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[RFC文档]]></category>
		<category><![CDATA[UrlEncode]]></category>
		<category><![CDATA[Url编码]]></category>
		<category><![CDATA[字符集]]></category>
		<category><![CDATA[编解码]]></category>

		<guid isPermaLink="false">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</guid>
		<description><![CDATA[摘要
<p>本文主要针对URI编解码的相关问题做了介绍，对Url编码中哪些字符需要编码、为什么需要编码做了详细的说明，并对比分析了Javascript中和编解码相关的几对函数escape / unescape,encodeURI / decodeURI和encodeURIComponent / decodeURICom&#8230;</p>]]></description>
			<content:encoded><![CDATA[<h2>摘要</h2>
<p>本文主要针对URI编解码的相关问题做了介绍，对Url编码中哪些字符需要编码、为什么需要编码做了详细的说明，并对比分析了Javascript中和编解码相关的几对函数escape / unescape,encodeURI / decodeURI和encodeURIComponent / decodeURIComponent。</p>
<h2>预备知识</h2>
<p>&#160;&#160; foo://example.com:8042/over/there?name=ferret#nose&#160; <br />&#160;&#160; \_/&#160; \______________/ \________/\_________/ \__/     <br />&#160;&#160;&#160;&#160; |&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; |&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; |&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; |&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; |     <br />scheme&#160;&#160;&#160;&#160; authority&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; path&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; query&#160;&#160;&#160;&#160;&#160; fragment</p>
<p>URI是统一资源标识的意思，通常我们所说的Url只是URI的一种。典型Url的格式如上面所示。下面提到的Url编码，实际上应该指的是URI编码。</p>
<h2>为什么需要Url编码</h2>
<p>通常如果一样东西需要编码，说明这样东西并不适合传输。原因多种多样，如Size过大，包含隐私数据，<strong><font color="#008000">对于Url来说，之所以要进行编码，是因为Url中有些字符会引起歧义</font></strong>。</p>
<p>例如Url参数字符串中使用key=value键值对这样的形式来传参，键值对之间以&amp;符号分隔，如/s?q=abc&amp;ie=utf-8。如果你的value字符串中包含了=或者&amp;，那么势必会造成接收Url的服务器解析错误，因此必须将引起歧义的&amp;和=符号进行转义，也就是对其进行编码。</p>
<p>又如，Url的编码格式采用的是ASCII码，而不是Unicode，这也就是说你不能在Url中包含任何非ASCII字符，例如中文。否则如果客户端浏览器和服务端浏览器支持的字符集不同的情况下，中文可能会造成问题。</p>
<p><strong><font color="#008000">Url编码的原则就是使用安全的字符（没有特殊用途或者特殊意义的可打印字符）去表示那些不安全的字符。</font></strong></p>
<h2>哪些字符需要编码</h2>
<blockquote><p>RFC3986文档规定，Url中只允许包含英文字母（a-zA-Z）、数字（0-9）、-_.~4个特殊字符以及所有保留字符。</p>
</blockquote>
<p>RFC3986文档对Url的编解码问题做出了详细的建议，指出了哪些字符需要被编码才不会引起Url语义的转变，以及对为什么这些字符需要编码做出了相应的解释。</p>
<h3>US-ASCII字符集中没有对应的可打印字符</h3>
<p>Url中只允许使用可打印字符。US-ASCII码中的10-7F字节全都表示控制字符，这些字符都不能直接出现在Url中。同时，对于80-FF字节（ISO-8859-1），由于已经超出了US-ACII定义的字节范围，因此也不可以放在Url中。</p>
<h3>保留字符</h3>
<p>Url可以划分成若干个组件，协议、主机、路径等。有一些字符（:/?#[]@）是用作分隔不同组件的。例如:冒号用于分隔协议和主机，/用于分隔主机和路径，?用于分隔路径和查询参数，等等。还有一些字符（!$&amp;'()*+,;=）用于在每个组件中起到分隔作用的，如=用于表示查询参数中的键值对，&amp;符号用于分隔查询多个键值对。当组件中的普通数据包含这些特殊字符时，需要对其进行编码。</p>
<p>RFC3986中指定了以下字符为保留字符：</p>
<table border="1" cellspacing="0" cellpadding="2" width="400">
<tbody>
<tr>
<td title="Exclamation mark">!</td>
<td title="Asterisk">*</td>
<td title="Apostrophe (mark)">'</td>
<td title="Bracket">(</td>
<td title="Bracket">)</td>
<td title="Semicolon">;</td>
<td title="Colon (punctuation)">:</td>
<td title="@">@</td>
<td title="Ampersand">&amp;</td>
<td title="Equal sign">=</td>
<td title="Plus sign">+</td>
<td title="Dollar sign">$</td>
<td title="Comma (punctuation)">,</td>
<td title="Slash (punctuation)">/</td>
<td title="Question mark">?</td>
<td title="Number sign">#</td>
<td title="Bracket">[</td>
<td title="Bracket">]</td>
</tr>
</tbody>
</table>
<h3>不安全字符</h3>
<p>还有一些字符，当他们直接放在Url中的时候，可能会引起解析程序的歧义。这些字符被视为不安全字符，原因有很多。</p>
<table border="1" cellspacing="0" cellpadding="2" width="575">
<tbody>
<tr>
<td valign="top" width="72">空格</td>
<td valign="top" width="501">Url在传输的过程，或者用户在排版的过程，或者文本处理程序在处理Url的过程，都有可能引入无关紧要的空格，或者将那些有意义的空格给去掉</td>
</tr>
<tr>
<td valign="top" width="72">引号以及&lt;&gt;</td>
<td valign="top" width="501">引号和尖括号通常用于在普通文本中起到分隔Url的作用</td>
</tr>
<tr>
<td valign="top" width="72">#</td>
<td valign="top" width="501">通常用于表示书签或者锚点</td>
</tr>
<tr>
<td valign="top" width="72">%</td>
<td valign="top" width="501">百分号本身用作对不安全字符进行编码时使用的特殊字符，因此本身需要编码</td>
</tr>
<tr>
<td valign="top" width="72">{}|\^[]`~</td>
<td valign="top" width="501">某一些网关或者传输代理会篡改这些字符</td>
</tr>
</tbody>
</table>
<p>&#160;</p>
<p><strong><font color="#008000">需要注意的是，对于Url中的合法字符，编码和不编码是等价的，但是对于上面提到的这些字符，如果不经过编码，那么它们有可能会造成Url语义的不同。因此对于Url而言，只有普通英文字符和数字，特殊字符$-_.+!*'()还有保留字符，才能出现在未经编码的Url之中</font></strong>。其他字符均需要经过编码之后才能出现在Url中。</p>
<p>但是由于历史原因，目前尚存在一些不标准的编码实现。例如对于~符号，虽然RFC3986文档规定，对于波浪符号~，不需要进行Url编码，但是还是有很多老的网关或者传输代理会</p>
<h2>如何对Url中的非法字符进行编码</h2>
<p>Url编码通常也被称为百分号编码（Url Encoding，also known as percent-encoding），是因为它的编码方式非常简单，使用%百分号加上两位的字符——0123456789ABCDEF——代表一个字节的十六进制形式。Url编码默认使用的字符集是US-ASCII。例如a在US-ASCII码中对应的字节是0x61，那么Url编码之后得到的就是%61，我们在地址栏上输入http://g.cn/search?q=%61%62%63，实际上就等同于在google上搜索abc了。又如@符号在ASCII字符集中对应的字节为0x40，经过Url编码之后得到的是%40。</p>
<p>常见字符的Url编码列表：</p>
<table border="1" cellspacing="0" cellpadding="6">
<caption>保留字符的Url编码</caption>
<tbody>
<tr>
<td>!</td>
<td>*</td>
<td>&quot;</td>
<td>'</td>
<td>(</td>
<td>)</td>
<td>;</td>
<td>:</td>
<td>@</td>
<td>&amp;</td>
</tr>
<tr>
<td><code>%21</code></td>
<td><code>%2A</code></td>
<td><code>%22</code></td>
<td><code>%27</code></td>
<td><code>%28</code></td>
<td><code>%29</code></td>
<td><code>%3B</code></td>
<td><code>%3A</code></td>
<td><code>%40</code></td>
<td><code>%26</code></td>
</tr>
<tr>
<td>=</td>
<td>+</td>
<td>$</td>
<td>,</td>
<td>/</td>
<td>?</td>
<td>%</td>
<td>#</td>
<td>[</td>
<td>]</td>
</tr>
<tr>
<td><code>%3D</code></td>
<td><code>%2B</code></td>
<td><code>%24</code></td>
<td><code>%2C</code></td>
<td><code>%2F</code></td>
<td><code>%3F</code></td>
<td><code>%25</code></td>
<td><code>%23</code></td>
<td><code>%5B</code></td>
<td><code>%5D</code></td>
</tr>
</tbody>
</table>
<p><strong><font color="#008000">对于非ASCII字符，需要使用ASCII字符集的超集进行编码得到相应的字节，然后对每个字节执行百分号编码</font></strong>。对于Unicode字符，RFC文档建议使用utf-8对其进行编码得到相应的字节，然后对每个字节执行百分号编码。如“中文”使用UTF-8字符集得到的字节为0xE4 0xB8 0xAD 0xE6 0x96 0x87，经过Url编码之后得到“%E4%B8%AD%E6%96%87”。</p>
<p><strong><font color="#008000">如果某个字节对应着ASCII字符集中的某个非保留字符，则此字节无需使用百分号表示</font></strong>。例如“Url编码”，使用UTF-8编码得到的字节是0x55 0x72 0x6C 0xE7 0xBC 0x96 0xE7 0xA0 0x81，由于前三个字节对应着ASCII中的非保留字符“Url”，因此这三个字节可以用非保留字符“Url”表示。最终的Url编码可以简化成“Url%E7%BC%96%E7%A0%81” ，当然，如果你用&quot;%55%72%6C%E7%BC%96%E7%A0%81”也是可以的。</p>
<p>由于历史的原因，有一些Url编码实现并不完全遵循这样的原则，下面会提到。</p>
<h2>Javascript中的escape,encodeURI和encodeURIComponent的区别</h2>
<p>Javascript中提供了3对函数用来对Url编码以得到合法的Url，它们分别是escape / unescape,encodeURI / decodeURI和encodeURIComponent / decodeURIComponent。由于解码和编码的过程是可逆的，因此这里只解释编码的过程。</p>
<p>这三个编码的函数——escape，encodeURI，encodeURIComponent——都是用于将不安全不合法的Url字符转换为合法的Url字符表示，它们有以下几个不同点。</p>
<h3>安全字符不同</h3>
<p>下面的表格列出了这三个函数的安全字符（即函数不会对这些字符进行编码）</p>
<table border="1" cellspacing="0" cellpadding="2" width="531">
<tbody>
<tr>
<td valign="top" width="214">&#160;</td>
<td valign="top" width="315">安全字符</td>
</tr>
<tr>
<td valign="top" width="214">escape（69个）</td>
<td valign="top" width="315">*/@+<font color="#000080">-._0-9a-zA-Z</font></td>
</tr>
<tr>
<td valign="top" width="214">encodeURI（82个）</td>
<td valign="top" width="315">!#$&amp;'()*+,/:;=?@<font color="#000080">-._~0-9a-zA-Z </font></td>
</tr>
<tr>
<td valign="top" width="214">encodeURIComponent（71个）</td>
<td valign="top" width="315">!'()*<font color="#000080">-._~0-9a-zA-Z</font></td>
</tr>
</tbody>
</table>
<h3>兼容性不同</h3>
<p>escape函数是从Javascript1.0的时候就存在了，其他两个函数是在Javascript1.5才引入的。但是由于Javascript1.5已经非常普及了，所以实际上使用encodeURI和encodeURIComponent并不会有什么兼容性问题。</p>
<h3>对Unicode字符的编码方式不同</h3>
<p>这三个函数对于ASCII字符的编码方式相同，均是使用百分号+两位十六进制字符来表示。但是对于Unicode字符，escape的编码方式是%u<i>xxxx</i>，其中的xxxx是用来表示unicode字符的4位十六进制字符。这种方式已经被W3C废弃了。但是在ECMA-262标准中仍然保留着escape的这种编码语法。<strong><font color="#008000">encodeURI和encodeURIComponent则使用UTF-8对非ASCII字符进行编码，然后再进行百分号编码</font></strong>。这是RFC推荐的。因此建议尽可能的使用这两个函数替代escape进行编码。</p>
<h3>适用场合不同</h3>
<p>encodeURI被用作对一个完整的URI进行编码，而encodeURIComponent被用作对URI的一个组件进行编码。</p>
<p>从上面提到的安全字符范围表格来看，我们会发现，encodeURIComponent编码的字符范围要比encodeURI的大。我们上面提到过，保留字符一般是用来分隔URI组件（一个URI可以被切割成多个组件，参考预备知识一节）或者子组件（如URI中查询参数的分隔符），如:号用于分隔scheme和主机，?号用于分隔主机和路径。由于encodeURI操纵的对象是一个完整的的URI，这些字符在URI中本来就有特殊用途，因此这些保留字符不会被encodeURI编码，否则意义就变了。</p>
<p>组件内部有自己的数据表示格式，但是这些数据内部不能包含有分隔组件的保留字符，否则就会导致整个URI中组件的分隔混乱。因此对于单个组件使用encodeURIComponent，需要编码的字符就更多了。</p>
<h2>表单提交</h2>
<p>当Html的表单被提交时，每个表单域都会被Url编码之后才在被发送。由于历史的原因，表单使用的Url编码实现并不符合最新的标准。例如对于空格使用的编码并不是%20，而是+号，如果表单使用的是Post方法提交的，我们可以在HTTP头中看到有一个Content-Type的header，值为application/x-www-form-urlencoded。大部分应用程序均能处理这种非标准实现的Url编码，但是在客户端Javascript中，并没有一个函数能够将+号解码成空格，只能自己写转换函数。还有，对于非ASCII字符，使用的编码字符集取决于当前文档使用的字符集。例如我们在Html头部加上</p>
<pre class="csharpcode"><span class="kwrd">&lt;</span><span class="html">meta</span> <span class="attr">http-equiv</span><span class="kwrd">=&quot;Content-Type&quot;</span> <span class="attr">content</span><span class="kwrd">=&quot;text/html; charset=gb2312&quot;</span> <span class="kwrd">/&gt;</span></pre>
<p>这样浏览器就会使用gb2312去渲染此文档（注意，当HTML文档中没有设置此meta标签，则浏览器会根据当前用户喜好去自动选择字符集，用户也可以强制当前网站使用某个指定的字符集）。当提交表单时，Url编码使用的字符集就是gb2312。</p>
<h2>文档字符集会影响encodeURI吗？</h2>
<p>之前在使用Aptana（为什么专指aptana下面会提到）遇到一个很迷惑的问题，就是在使用encodeURI的时候，发现它编码得到的结果和我想的很不一样。下面是我的示例代码：</p>
<pre class="csharpcode">

<span class="kwrd">&lt;!</span><span class="html">DOCTYPE</span> <span class="attr">html</span> <span class="attr">PUBLIC</span> <span class="kwrd">&quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot;</span> <span class="kwrd">&quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;</span><span class="html">html</span> <span class="attr">xmlns</span><span class="kwrd">=&quot;http://www.w3.org/1999/xhtml&quot;</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">head</span><span class="kwrd">&gt;</span>
        <span class="kwrd">&lt;</span><span class="html">meta</span> <span class="attr">http-equiv</span><span class="kwrd">=&quot;Content-Type&quot;</span> <span class="attr">content</span><span class="kwrd">=&quot;text/html; charset=gb2312&quot;</span> <span class="kwrd">/&gt;</span>
    <span class="kwrd">&lt;/</span><span class="html">head</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;</span><span class="html">body</span><span class="kwrd">&gt;</span>
        <span class="kwrd">&lt;</span><span class="html">script</span> <span class="attr">type</span><span class="kwrd">=&quot;text/javascript&quot;</span><span class="kwrd">&gt;</span>
            document.write(encodeURI(<span class="str">&quot;中文&quot;</span>));
        <span class="kwrd">&lt;/</span><span class="html">script</span><span class="kwrd">&gt;</span>
    <span class="kwrd">&lt;/</span><span class="html">body</span><span class="kwrd">&gt;</span>
<span class="kwrd">&lt;/</span><span class="html">html</span><span class="kwrd">&gt;</span>

<span class="kwrd"></span>&#160;
</pre>
<p>运行结果输出%E6%B6%93%EE%85%9F%E6%9E%83。显然这并不是使用UTF-8字符集进行Url编码得到的结果（在Google上搜索“中文”，Url中显示的是%E4%B8%AD%E6%96%87）。</p>
<p>所以我当时就很质疑，难道encodeURI还跟页面编码有关，但是我发现，正常情况下，如果你使用gb2312进行Url编码也不会得到这个结果的才是。后来终于被我发现，原来是<strong><font color="#008000">页面文件存储使用的字符集和Meta标签中指定的字符集不一致导致的问题</font></strong>。Aptana的编辑器默认情况下使用UTF-8字符集。也就是说这个文件实际存储的时候使用的是UTF-8字符集。但是由于Meta标签中指定了gb2312，这个时候，浏览器就会按照gb2312去解析这个文档，那么自然在“中文”这个字符串这里就会出错，因为“中文”字符串用UTF-8编码过后得到的字节是0xE4 0xB8 0xAD 0xE6 0x96 0x87，这6个字节又被浏览器拿gb2312去解码，那么就会得到另外三个汉字“涓枃”（GBK中一个汉字占两个字节），这三个汉字在传入encodeURI函数之后得到的结果就是%E6%B6%93%EE%85%9F%E6%9E%83。因此，encodeURI使用的还是UTF-8，并不会受到页面字符集的影响。</p>
<h2>其他和Url编码相关的问题</h2>
<p>对于包含中文的Url的处理问题，不同浏览器有不同的表现。例如对于IE，如果你勾选了高级设置“总是以UTF-8发送Url”，那么Url中的路径部分的中文会使用UTF-8进行Url编码之后发送给服务端，而查询参数中的中文部分使用系统默认字符集进行Url编码。为了保证最大互操作性，建议所有放到Url中的组件全部显式指定某个字符集进行Url编码，而不依赖于浏览器的默认实现。</p>
<p>另外，很多HTTP监视工具或者浏览器地址栏等在显示Url的时候会自动将Url进行一次解码（使用UTF-8字符集），这就是为什么当你在Firefox中访问Google搜索中文的时候，地址栏显示的Url包含中文的缘故。但实际上发送给服务端的原始Url还是经过编码的。你可以在地址栏上使用Javascript访问location.href就可以看出来了。在研究Url编解码的时候千万别被这些假象给迷惑了。</p>
<p align="right">——<a href="http://www.imkevinyang.com/"><em><strong>Kevin Yang</strong></em></a></p>

	标签：<a href="http://www.imkevinyang.com/tags/javascript" title="Javascript" rel="tag">Javascript</a>, <a href="http://www.imkevinyang.com/tags/rfc%e6%96%87%e6%a1%a3" title="RFC文档" rel="tag">RFC文档</a>, <a href="http://www.imkevinyang.com/tags/urlencode" title="UrlEncode" rel="tag">UrlEncode</a>, <a href="http://www.imkevinyang.com/tags/url%e7%bc%96%e7%a0%81" title="Url编码" rel="tag">Url编码</a>, <a href="http://www.imkevinyang.com/categories/techarticles/web%e4%bc%a0%e7%bb%9f%e6%8a%80%e6%9c%af" title="Web传统技术" rel="tag">Web传统技术</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> </li>
	<li><a href="http://www.imkevinyang.com/2009/11/button%e6%a0%87%e7%ad%be%e9%bc%a0%e6%a0%87%e7%82%b9%e5%87%bb%e4%ba%8b%e4%bb%b6%e7%9a%84%e8%a7%a6%e5%8f%91%e6%ba%90%e9%97%ae%e9%a2%98.html" title="Button标签鼠标点击事件的触发源问题 (2009/11/27)">Button标签鼠标点击事件的触发源问题</a> </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> </li>
	<li><a href="http://www.imkevinyang.com/2009/03/ie%e4%b8%ad%e4%bd%bf%e7%94%a8windowopen%e6%89%93%e5%bc%80%e6%96%b0%e7%aa%97%e5%8f%a3%e6%97%b6%e6%97%a0%e6%b3%95%e8%8e%b7%e5%8f%96referrer%e5%af%b9%e8%b1%a1.html" title="IE中使用window.open打开新窗口时无法获取Referrer对象 (2009/03/07)">IE中使用window.open打开新窗口时无法获取Referrer对象</a> </li>
	<li><a href="http://www.imkevinyang.com/2010/07/javajs%e5%a6%82%e4%bd%95%e4%bd%bf%e7%94%a8%e6%ad%a3%e5%88%99%e8%a1%a8%e8%be%be%e5%bc%8f%e5%8c%b9%e9%85%8d%e5%b5%8c%e5%a5%97html%e6%a0%87%e7%ad%be.html" title="Java/Js如何使用正则表达式匹配嵌套Html标签 (2010/07/30)">Java/Js如何使用正则表达式匹配嵌套Html标签</a> </li>
	<li><a href="http://www.imkevinyang.com/2009/07/javascript-%e4%b8%ad%e7%9a%84false%e9%9b%b6%e5%80%bcnullundefined%e5%92%8c%e7%a9%ba%e5%ad%97%e7%ac%a6%e4%b8%b2%e5%af%b9%e8%b1%a1.html" title="Javascript 中的false,零值,null,undefined和空字符串对象 (2009/07/07)">Javascript 中的false,零值,null,undefined和空字符串对象</a> </li>
	<li><a href="http://www.imkevinyang.com/2009/05/javascript%e4%b8%ad%e8%8e%b7%e5%8f%96%e5%87%ba%e9%94%99%e4%bb%a3%e7%a0%81%e6%89%80%e5%9c%a8%e6%96%87%e4%bb%b6%e5%8f%8a%e8%a1%8c%e6%95%b0.html" title="Javascript中获取出错代码所在文件及行数 (2009/05/18)">Javascript中获取出错代码所在文件及行数</a> </li>
	<li><a href="http://www.imkevinyang.com/2009/04/javascript%e5%ad%97%e7%ac%a6%e4%b8%b2%e5%93%88%e5%b8%8c%e5%87%bd%e6%95%b0.html" title="Javascript字符串哈希函数 (2009/04/11)">Javascript字符串哈希函数</a> </li>
	<li><a href="http://www.imkevinyang.com/2009/06/javascript%e6%93%8d%e7%ba%b5cookie.html" title="Javascript操纵Cookie (2009/06/11)">Javascript操纵Cookie</a> </li>
	<li><a href="http://www.imkevinyang.com/2010/05/%e4%b8%ba%e4%bb%80%e4%b9%88iis77-5%e7%9a%84gzip%e4%b8%8d%e8%b5%b7%e4%bd%9c%e7%94%a8.html" title="为什么IIS7/7.5的Gzip不起作用 (2010/05/08)">为什么IIS7/7.5的Gzip不起作用</a> </li>
</ul>

]]></content:encoded>
			<wfw:commentRss>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/feed</wfw:commentRss>
		<slash:comments>4</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><span style="color: #ff0000;">（关于字符编码的深入解释，请参见我的原创文章《<a title="关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312…）" 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" target="_self">关于字符编码，你所需要知道的</a>》。）</span></p>
<p><span style="color: #ff0000;">此文为转载，有少许修订，原文出处不详。</span></p>
<p>很久很久以前，有一群人，他们决定用8个可以开合的晶体管来组合成不同的状态，以表示世界上的万物。他们认为8个开关状态作为原子单位很好，于是他们把这称为"字节"。</p>
<p>再后来，他们又做了一些可&#8230;</p>]]></description>
			<content:encoded><![CDATA[<p><span style="color: #ff0000;">（关于字符编码的深入解释，请参见我的原创文章《<a title="关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312…）" 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" target="_self">关于字符编码，你所需要知道的</a>》。）</span></p>
<p><span style="color: #ff0000;">此文为转载，有少许修订，原文出处不详。</span></p>
<p>很久很久以前，有一群人，他们决定用8个可以开合的晶体管来组合成不同的状态，以表示世界上的万物。他们认为8个开关状态作为原子单位很好，于是他们把这称为"字节"。</p>
<p>再后来，他们又做了一些可以处理这些字节的机器，机器开动了，可以用字节来组合出更多的状态，状态开始变来变去。他们看到这样是好的，于是它们就这机器称为"计算机"。</p>
<p>开始计算机只在美国用。八位的字节一共可以组合出256（2的8次方）种不同的状态。</p>
<p>他们把其中的编号从0开始的32种状态分别规定了特殊的用途，一但终端设备或者打印机遇上这些约定好的字节时，就要做一些约定的动作。遇上 00x10, 终端就换行，遇上0x07, 终端就向人们嘟嘟叫，例好遇上0x1b, 打印机就打印反白的字，对于终端就用彩色显示字母。他们看到这样很好，于是就把这些0x20（十进制32）以下的字节状态称为"控制码"。</p>
<p>他们又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示，一直编到了第127号，这样计算机就可以用不同字节来存储英语的 文字了。大家看到这样，都感觉很好，于是大家都把这个方案叫做 ANSI 的"Ascii"编码（American Standard Code for Information Interchange，美国信息互换标准代码）。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。</p>
<p>后来，就像建造巴比伦塔一样，世界各地的都开始使用计算机，但是很多国家用的不是英文，他们用到的许多字母在ASCII中根本没有，为了也可以在计算机中保存他们的文字，他们决定采用127号之后的空位来表示这些新的字母、符号，还加入了很多画表格时需要用下到的横线、竖线、交叉等形状，一直把序号编到了最后一个状态255。从128到255这一页的字符集被称"扩展字符集"。从此之后，贪婪的人类再没有新的状态可以用了，美帝国主义可能没有想到还有第三世界国家的人们也希望可以用到计算机吧！</p>
<p>等中国人们得到计算机时，已经没有可以利用的字节状态来表示汉字，况且有6000多个常用汉字需要保存呢。但是这难不倒智慧的中国人民，我们不客气地把那些127号之后的奇异符号们直接取消掉，并且规定：一个小于127的字符的意义与原来相同，但两个大于127的字符连在一起时，就表示一个汉字，前面的一个字节（他称之为高字节）从0xA1用到 0xF7，后面一个字节（低字节）从0xA1到0xFE，这样我们就可以组合出大约7000多个简体汉字了。在这些编码里，我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了，连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码，这就是常说的"全角"字符，而原来在127号以下的那些就叫"半角"字符了。</p>
<p>中国人民看到这样很不错，于是就把这种汉字方案叫做"GB2312"。GB2312 是对 ASCII 的中文扩展。</p>
<p>但是中国的汉字太多了，我们很快就就发现有许多人的人名没有办法在这里打出来，特别是某些很会麻烦别人的国家领导人（如朱镕基的“镕”字）。于是我们不得不继续把 GB2312 没有用到的码位找出来老实不客气地用上。</p>
<p>后来还是不够用，于是干脆不再要求低字节一定是127号之后的内码，只要第一个字节是大于127就固定表示这是一个汉字的开始，不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准，GBK 包括了 GB2312 的所有内容，同时又增加了近20000个新的汉字（包括繁体字）和符号。</p>
<p>后来少数民族也要用电脑了，于是我们再扩展，又加了几千个新的少数民族的字，GBK 扩成了 GB18030。从此之后，中华民族的文化就可以在计算机时代中传承了。</p>
<p>中国的程序员们看到这一系列汉字编码的标准是好的，于是通称他们叫做 "DBCS"（Double Byte Charecter Set 双字节字符集）。在DBCS系列标准里，最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里，因此他们写的程序为了支持中文处理，必须要注意字串里的每一个字节的值，如果这个值是大于127的，那么就认为一个双字节字符集里的字符出现了。那时候凡是受过加持，会编程的计算机僧侣们都要每天念下面这个咒语数百遍：</p>
<p>"一个汉字算两个英文字符！一个汉字算两个英文字符……"</p>
<p>因为当时各个国家都像中国这样搞出一套自己的编码标准，结果互相之间谁也不懂谁的编码，谁也不支持别人的编码，连大陆和台湾这样只相隔了150海里，使用着同一种语言的兄弟地区，也分别采用了不同的 DBCS 编码方案——当时的中国人想让电脑显示汉字，就必须装上一个"汉字系统"，专门用来处理汉字的显示、输入的问题，但是那个台湾的愚昧封建人士写的算命程序就必须加装另一套支持 BIG5 编码的什么"倚天汉字系统"才可以用，装错了字符系统，显示就会乱了套！这怎么办？而且世界民族之林中还有那些一时用不上电脑的穷苦人民，他们的文字又怎么办？</p>
<p>真是计算机的巴比伦塔命题啊！</p>
<p>正在这时，大天使加百列及时出现了——一个叫 ISO （国际标谁化组织）的国际组织决定着手解决这个问题。他们采用的方法很简单：废了所有的地区性编码方案，重新搞一个包括了地球上所有文化、所有字母和符号的编码！他们打算叫它"Universal Multiple-Octet Coded Character Set"，简称 UCS, 俗称 "UNICODE"。</p>
<p>UNICODE 开始制订时，计算机的存储器容量极大地发展了，空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节，也就是16位来统一表示所有的字符，对于ascii里的那些"半角"字符，UNICODE 包持其原编码不变，只是将其长度由原来的8位扩展为16位，而其他文化和语言的字符则全部重新统一编码。由于"半角"英文符号只需要用到低8位，所以其高 8位永远是0，因此这种大气的方案在保存英文文本时会多浪费一倍的空间。</p>
<p>这时候，从旧社会里走过来的程序员开始发现一个奇怪的现象：他们的strlen函数靠不住了，一个汉字不再是相当于两个字符了，而是一个！是 的，从 UNICODE 开始，无论是半角的英文字母，还是全角的汉字，它们都是统一的"一个字符"！同时，也都是统一的"两个字节"，请注意"字符"和"字节"两个术语的不同， "字节"是一个8位的物理存贮单元，而"字符"则是一个文化相关的符号。在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 架构；而另一些是采用高位先发送的方式。在网络中交换数据时，为了核对双方对于高低位的认识是否是一致的，采用了一种很简便的方法，就是在文本流的开始时向对方发送一个标志符——如果之后的文本是高位在位，那就发送"FEFF"，反之，则发送"FFFE"。不信你可以用二进制方式打开一个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>例如"汉"字的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 的记事本里新建一个文件，输入"联通"两个字之后，保存，关闭，然后再次打开，你会发现这两个字已经消失了，代之的是几个乱码！呵呵，有人说这就是联通之所以拼不过移动的原因。</p>
<p>其实这是因为GB2312编码与UTF8编码产生了编码冲撞的原因。</p>
<p>当一个软件打开一个文本时，它要做的第一件事是决定这个文本究竟是使用哪种字符集的哪种编码保存的。软件一般采用三种方式来决定文本的字符集和编码：</p>
<p>检测文件头标识，提示用户选择，根据一定的规则猜测</p>
<p>最标准的途径是检测文本最开头的几个字节，开头字节 Charset/encoding,如下表：</p>
<blockquote><p>EF BB BF UTF-8</p>
<p>FF FE UTF-16/UCS-2, little endian</p>
<p>FE FF 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系列的编码方式，在这种编码下，"联通"的内码是：</p>
<blockquote><p>c1 1100 0001</p>
<p>aa 1010 1010</p>
<p>cd 1100 1101</p>
<p>a8 1010 1000</p></blockquote>
<p>注意到了吗？第一二个字节、第三四个字节的起始部分的都是"110"和"10"，正好与UTF8规则里的两字节模板是一致的，</p>
<p>于是当我们再次打开记事本时，记事本就误认为这是一个UTF8编码的文件，让我们把第一个字节的110和第二个字节的10去掉，我们就得到了"00001 101010"，再把各位对齐，补上前导的0，就得到了"0000 0000 0110 1010"，不好意思，这是UNICODE的006A，也就是小写的字母"j"，而之后的两字节用UTF8解码之后是0368，这个字符什么也不是。这就是只有"联通"两个字的文件没有办法在记事本里正常显示的原因。</p>
<p>而如果你在"联通"之后多输入几个字，其他的字的编码不见得又恰好是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> </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> </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> </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="关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312&#8230;） (2010/06/18)">关于字符编码，你所需要知道的（ASCII,Unicode,Utf-8,GB2312&#8230;）</a> </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> </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> </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> </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> </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>6</slash:comments>
		</item>
	</channel>
</rss>

<!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Page Caching using disk: enhanced

Served from: www.imkevinyang.com @ 2012-02-08 19:33:04 -->
