分享

中文字符编码的相互转换(二)

 海漩涡 2014-12-24

转自:http://blog.csdn.net/yulongli/article/details/22952127

中文字符编码的相互转换(二)

分类: 基础知识 373人阅读 评论(0) 收藏 举报
这里说一下因为编码带来的存储问题。

存储有两层含义,一是指落地成文件存储,这种情况一般用户都会遇到;另外一层指的是在内存中的存储,这是程序员要考虑的事情。

因为字符编码有好多种,如果一段文本中有非ASCII字符,就必须得用某一种字符编码来表示,将其保存到文件中也是如此。

现在要打开一个文本文件,文件编辑器(记事本,EditPlus等)必须要知道这个文件采用了何种编码,不然没法解析。如果文件是UTF-8编码存储,却被当作GBK编码来解读,最后展示出来肯定会是乱码。

我们先说Windows操作系统下面的情况,GBK是中文版Windows默认的兼容ASCII码的存储格式,所以GBK编码不用特殊标识。只不过这样的文件放到台湾版或是日本版的系统下面打开会是乱码。UTF-8编码的文件就需要特殊处理一下,Windows会在UTF-8编码的文件前增加3个字节的特殊字符"EF BB BF” ,这就是常说的BOM,全称叫做"Byte Order Mard”。

只是,BOM是为其他编码准备的,UTF-8带BOM并不合标准,只在Windows阵营下才这么用。由于Windows的影响范围太广,很多文本编辑器都是支持带BOM的UTF-8文本。在Mac和Linux下就不吃这一套,很有可能会出现乱码。大部分编译器也不认,遇到问题会直接编译错误。

我们可以用Windows自带的记事本来存储不同格式的文本并进行比较。打开记事本,输入一段文本,比如就三个字“中国人”,保存时可以指定编码,如下图:



其中ANSI默认就是GBK编码,Unicode和UTF-8分别是相应的编码。而出现的big endian 还有默认的little endian指的存储格式,接下来会再做简单的介绍。弄不明白也没关系,这属于编码中比较边缘的知识,不用急于一时全部搞懂。

保存完后我们来看分别选择ANSI 、 Unicode 和 UTF-8 这三种情况下文件的具体内容,可以用UltraEdit打开看文件看其16进制表示,也可以用Linux下面的xxd工具。

用GBK 编码保存后的文件内容如下:
0000000: d6d0 b9fa c8cb                           ……
可以看到“中国人”三个字的GBK编码分别是“d6 d0”, “b9 fa”, “c8 cb”

用UTF-8编码保存后的文件内容如下:
0000000: efbb bfe4 b8ad e59b bde4 baba            …………
可以看到“中国人”三个字的UTF-8编码分别是“e4 b8 ad” “e5 9b bd” “e4 ba ba”, 前面多出来的3个字节是BOM。
如果在Mac下面重新保存一个文件,里面的内容是这样的,没有BOM,“0a”是最后多了一个换行符

0000000: e4b8 ade5 9bbd e4ba ba0a                 ……….

用Unicode编码保存后的文件内容如下:
0000000: fffe 2d4e fd56 ba4e                      ..-N.V.N
“中国人”三个字的Unicode编码分别是 “0x4E2D” “0x56FD”, “0x4EBA”
其中fffe代表该文件是little endian存储的,如果是feff,说明是big endian 存储的。

看到这里大家可能又奇怪了,怎么GBK 跟UTF-8的编码跟展示那么对应,Unicode展示出来就高低位互换了呢?

这里要格外注意,GBK和UTF-8的基本单位是byte,是一个字节,编辑器对其解析时是顺序的。我们看到的从左到右在地址空间里面是由低位到高位。“中”的GBK编码就就是0xd6和0xd0两个字节拼起来表示。但Unicode的基本单位是short,也就是两个字节,“中”的Unicode表示是”0x4E2D”, 2D在低位,用little endian存储当然是在左边了。

Mac 下面默认是big endian存储,用Unicode编码保存后的文件内容如下:
0000000: 4e2d 56fd 4eba 000a                      N-V.N…
可以看到2D跑到右边去了。这里最后面的“000a” 是Unicode表示的换行符,其在正常ASCII码的前面增加了8位的0。

再说一下HTML页面,也就是我们平常说的网页。在文件开头有meta标签可以设置charset,这是设置本页面编码的地方,它的值可以为"utf-8”,”gb2312”等。charset一定要在文件开头,在任何非ASCII码出现之前设置好,否则很有可能出现乱码。另外,如果是UTF-8格式存储,BOM是不需要的。有些浏览器碰到带BOM的页面会解析出错。

比如下面这个HTML页面的代码。
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-strict.dtd">  
  2. <html xmlns="http://www./1999/xhtml">  
  3. <head>  
  4.      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
  5.      <title>标题,请写在charset设置之后</title>   
  6. </head>  
  7.   
  8. <body>  
  9.      Hello HTML! 这里是Body内容,肯定在charset设置之后!  
  10. </body>  
  11.   
  12. </html>  

网络上还有一种XML文件使用的很频繁,其编码的设置跟HTML文件差不多,也是在开头设置encoding。不过对于web开发工程师来说,如果是利用xml实现ajax请求,后台返回给前端的xml数据可以不必太纠结编码,统一用unicode的转义字符来表示就可以了。浏览器自己可以正常解析,这部分内容我们后续再详细介绍。


比如下面这个xml文件,不论encoding设置成gb2312还是utf-8,浏览器都可以正常显示。

  1. <?xml version="1.0" encoding="gb2312"?>  
  2. <node name="&#x4E2D;&#x56FD;&#x4EBA;"></node>  

只是这种情况如果是程序员写代码来解析的话,最好还是将所使用的编码格式跟charset声明的保持一致。

再说一下内存中的编码。先看一下下面这段C代码:

  1. #include <stdio.h>  
  2. #include <string.h>  
  3. int main(){  
  4.      int i=0;  
  5.      char str[100] = "中国人";  
  6.      int len = strlen(str);  
  7.      printf("the length of str is %d\n", len);  
  8.      for (i=0; i<len; i++){  
  9.           printf("%X ", (unsigned char)str[i]);  
  10.   
  11.      }  
  12.      printf("\n");  
  13.      return 0;  
  14. }  
编译后执行,看看输出结果是啥。当然结果是未知的,因为常见的会有以下两种情况:
第一种:

the length of str is 6 

D6 D0 B9 FA C8 CB


第二种:

the length of str is 9 

E4 B8 AD E5 9B BD E4 BA BA


之所以会出现这两种情况,就是因为str所指向的字符串其内容受源代码文件的编码格式影响,如果是以gbk编码保存,则执行结果是第一种情况,如果是用utf-8编码保存,就是第2种情况。

为了使编码固定,可以在定义时明确编码,比如我就是想定义一个utf8格式的编码,可以这么定义:
  1. char str[100] = "\xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba”;   
这种方式跟第二种方式是一模一样的,除了阅读不方便之外,编译之后对于计算机而言没有任何区别。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多