web中的字符编码
web开发中经常遇到各种乱码问题,原因都是编码不一致造成的。字符编码究竟起什么作用,如何运作的呢?先来了解一些基本概念:
字符集、编码字符集、字符编码
字符集
字符集就是一些字符的集合,比如我们使用的中文就可以看成一种字符集
编码字符集
是指每个字符都有唯一数字与之对应的字符集,每个单元编码字符被称作码点。码点的值表示着字符所在字符集中的位置,这样我们就可以通过具体的数字根据编码字符集找到相应的字符。
字符编码
字符编码表示这编码字符集码点值如何被映射成计算机中的字节。对于编码字符集,虽然对每个字符有了唯一的索引,但是要表示在计算机中,必须以字节的形式表示,而字符编码,正是表示这一映射过程。这样计算机就可以跟进字节和码点值的映射关系跟进字符集找到相应的字符。当然我们可以简单的将码点值直接转换成对应的计算机字节,码点值是65在计算机中也用65表示,这种编码方式最简单,ISO 8859系列字符集就是采用这种编码方式;但是为了优化之一映射过程还有其他编码方式,同一编码字符集也可以有多种编码方式,比如我们熟悉的Unicode字符集,就有UTF-8, UTF-16, and UTF-32编码方式,不同编码方式映射到计算机表示的字节就有可能不同。
字符编码就是计算机找到对应字符的钥匙,钥匙指定错了,计算机找到字符也就不正确了,这就是我们遇到乱码的原因。
在web中又是如何指定字符编码的呢?我们再来看下HTTP Header中关于字符的几个字段:
HTTP Header
Accept-Charset
Accept-Charset 字段表示可以响应采用哪些字符集编码是可以接受。服务器可以跟据这个字段了解客户端接受字符集的能力。当服务器不能提供相应字符集编码的数据时,就会返回406状态码,或者一个unacceptable response。这个字段平常打交道比较少,再看另一个非常重要的字段:
Content-Type
content-type字段标识着响应正文的media-type,已经采用的字符集编码。charset非常重要,因为接收端正是根据这一信息来对响应正文进行解码。设置一个和文档实际编码一致的charset就可以保证接受端能够正确的解码,也就是不会出现乱码。另外,需要注意的是,在html,css中这一参数声明会覆盖文档内的字符编码声明。
好了,了解了这些基本知识,我们具体看看html中设置编码问题:
HTML
html中我们可以通过meta 标签设置文档字符编码如:<meta charset=”gbk”/>,浏览器确定文档编码的过程简化说是这样的[1]:
- 如果用户在客户端明确指定了字符编码,客户端可以确定编码,停止检测(这一步可选)
- 如果http header指定字符编码,客户端确定编码,停止检测(正如上面所说,content-type charset会覆盖文档内的字符声明)
- 客户端查找文件编码信息meta charset/
http-equiv/content,如果找到确定编码为相应值,停止解析 - 最终如果无法确定编码,使用用户的默认编码,用户默认字符编码和本地语言相关,如中文语言默认编码使用GB18030
所以指定content-type或者meta charset情况下,并且和文件本身编码一致,浏览器就可以解析出正确字符。如果没用明确指定字符编码,就会产生乱码的风险,虽然浏览器也做了一些检测工作。
html文件中编码我们一般不会遇到乱码,再来看看比较常见的外链css/script中确定编码的过程:
css/script
外链css文件可以在文件开始设置@charset “charset”来指定字符编码,外链script可以设置script标签的charset属性指定字符编码,script具体确定过程是这样的[2]:
- 如果content-type指定,使用相应编码,停止检测
- 如果charset属性指定,使用相应编码,停止检测
- 最终如果无法确定编码使用当前文档的编码作为文件编码
css应该和script确定过程大致相同,一般情况下我们保证外链样式编码和文档一致即可或者手动设置@charset,script在动态请求时遇到乱码的情况会比较常见,尤其在请求内容不可控的情况下,如果响应方没用指定content-type,需要手动设置动态创建script的charset属性
再来看看最常见乱码的情况ajax中编码:
xmlHttpRequest
xhr请求数据并解析响应,在发送数据和接受数据时都可能会遇到乱码的问题,分开看一下:
send(data)
首先xhr提供了setRequestHeader方法,我们可以设定请求头,因此可以设定content-type,发送数据字符编码过程[3]:
- 如果data是 document,mime将使用
application/xml;charset=文档的编码 - 如果data是 string,将会对字符转码成utf8,mime使用 “
text/plain;charset=UTF-8“ - 如果通过
setRequestHeader设置了mime,那么charset参数会被设成utf-8,type保留,否则使用前面的mime type
可见发送数据,xhr保证了字符编码和实际编码一致(都为utf-8),这样接收端可以正确的解析字符,接收端出现乱码一般都没用按照正确的编码解析,或者又按另一种编码输出,而没有转换编码
Response
首先说下xhr的overrideMineType方法,允许覆盖相应的mine type,(目前仅ff,chrome支持),接收数据的编码大致[3]:
- 采用 overideMineType设置的编码 ,结束
- 如果响应设置了
Content-Type 采用对应编码,结束 - 如果mine type是 text/html 采用 html的编码确定方式
- 采用UTF-8
由于overideMineType兼容性问题,让服务端输出正确的content-type是比较简单有效的方法,当然还有其他一下方法,这里不讨论了
总结
保证设定的字符编码和文件真实编码一致,即可保证计算机解析出正确的字符;这也是产生乱码问题的根源;设定http content-type 即可指定数据的编码,客户端确定编码的优先级最高;如果没用设置content-type,对于html、css、script都有相应的指定编码的方式,对于xhr,发送的数据总是以utf-8编码,发送与实际字符编码一致(和文档编码无关),接收数据如果没用指定content-type,“最终”会以utf-8处理。总之,必须指定文件真实编码一致的字符编码,来保证计算机能够正确识别字符,避免乱码。
其他一些参考:
W3C处理html字符编码的文章: http://www.w3.org/International/tutorials/tutorial-char-enc/
overideMineType兼容方案:http://www.cnblogs.com/cuixiping/archive/2008/03/22/1118226.html
注1:具体步骤参见:http://dev.w3.org/html5/spec/parsing.html#determining-the-character-encoding
注2:具体步骤参见http://dev.w3.org/html5/spec/scripting-1.html#the-script-block-s-source
注3:具体步骤参见http://www.w3.org/TR/XMLHttpRequest/