本文目的
- 介绍工作中常见字符编码,主要涉及ASNI,GB2312,GBK,Unicode,UTF8。对于网页上的中文乱码现象,具有参考价值。
- 分享工作中遇到的中文乱码现象和解决方案
- 介绍如何使用iconv字符编码转换工具和一个简单的iconv.h的C++ wrapper
常见编码介绍
格式 |
特征 |
描述 |
ANSII |
单字节,范围0-127 |
可以描述所有的英文字母,阿拉伯数字,常用符号和控制符(回车,换行等) |
ANSII 扩展字符集 |
单字节,范围128-255 |
包括了一些不常用的字符,比如画表格时需要用下到的横线、竖线、交叉等形状。 它是ANSII的扩展。 |
GB2312 |
双字节,高位字节(第一个)范围:0xA1 ~ 0xF7, 低位字节范围:0xA1 ~ 0xFE |
对ANSII的中文扩展,兼容ANSII,不兼容ANSII扩展。 主要用于表达汉字,可以表达7000多个汉字,常用汉字有6000,所以包含了常用汉字,多的字符将罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,称为“全角”字符,ANSI原有的称为“半角”。 |
GBK |
双字节,高位字节范围0x80~0xFF,低位字节0x00~0xFF |
对GBK2312的扩展,包含不常见汉字,兼容GB2312,所以也兼容ANSII。通常Windows中文版本默认的字符集是GBK。 基本上包含了中华名族所有的汉字,如繁体,简体,少数名族的文字等等。 |
Unicode |
双字节,高位字节范围0x00~0xFF,低位字节0x00~0xFF |
用于标识地球上所有名族语言,不兼容上面的编码(ANSI,GB2312和GBK)。目的是将全世界所有的编码统一。对于英文而言,浪费了一倍的空间。 |
UTF-8 |
Unicode 向 UTF-8 转换模版: |
用于将Unicode在网上传输,每次传输8个bit。 全称Unicode Transfer Format -8。左边是unicode到utf8的转换模版。任何unicode按照不同区间的模版,按顺序填入自己的bit,就是对应的utf-8。 例如"汉"字的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的编码。 UTF8表示英文时,不会浪费空间,并且兼容ANSI,所以英文网页一般用UTF8编码。但是UTF8表示中文时,会浪费空间(每个汉字可能需要3个字节),所以一般中文网站采用GBK编码,节省带宽资源。 |
网页中文乱码
网页中出现中文乱码十分常见,主要是由于html标签中charset的设置与实际上的编码不一致导致,如图:
Charset告诉浏览器应该以什么格式解读html中内容,所以如果charset中的编码是utf-8,而html页面中的内容出现了gbk文本,由于两种格式不兼容,导致中文乱码,由于UTF-8,兼容ANSI,所以英文内容正常显示。从上面的表格,可以发现除了unicode不兼容ANSI,其他格式均兼容,所以很少遇见英文乱码现象。
工作中,曾经遇见以下几种乱码现象,现在总结出来与大家分享:
1. 数据源格式不同 html页面展示的数据来自不同的数据源,不同的数据源的数据编码格式不一样,那么无论charset设置什么值,都会是乱码。解决方法就是在展示数据之前,将所有的数据内容重新编码为统一的格式,如utf-8,让后设定charset=utf-8。
2. Html编码与数据源不同 编辑html的格式与数据源格式不一致,比如html编辑器默认使用了ANSI(gbk),而数据源(如数据库,xml,或第三方数据)是utf8,在编辑html时,为了不乱码显示,必然将charset设置为gbk或gb2312,所以当展示数据时,必然出现乱码。解决方法还是统一编码,如果数据源无法控制,可以将html设置为统一格式,如果html太多,那么需要借助批量编码转换工具。
3. CGI编码与数据源不同 CGI(C++,php等)代码的格式与数据源,charset不一致。动态网站html有可能是cgi生成的,在编写cgi时有可能会hard code一些中文内容,如果编写代码的格式与charset,或数据源不一致,那么必然出现乱码。
总结:确保html,CGI,数据源的编码格式与charset一致,避免网页中文乱码。
Iconv能做什么
Iconv是一个linux自带的编码转换工具,可以通过命令行手动转换文件,也可以通过提供的C语言接口,在程序中调用。在linux上使用命令”man iconv”,可以得到详细的iconv使用说明,这里就不再详细描述。
通过“man iconv.h”,可以看到iconv的C语言接口。这里需要指出的是,iconv固然强大,但是提供的C语言接口使用起来不方便,所以下面提供了一个简单的C++ warpper,简化了iconv的调用方式。
首先,简单的分析一些iconv原始接口的执行情况,可以在linux上输入命令“man 3 iconv”,然后简单浏览一下,发现调用iconv结束后会出现下面四种情况:
- 全部解析成功,inbyteslef为0,返回转化次数(non-reversible conventions performaned during the call)。
- 缓存空间不够,返回“(size_t)-1”,errno设置为E2BIG
- 输入数据不完全,返回“(size_t)-1”,errno设置为EINVAL
- 输入的数据格式不正确,返回“(size_t)-1”,errno设置为EILSEQ。
一旦知道了这四种情况,wrapper的代码就十分清楚了,下面是封装的代码:
size_t MIConv::Convert(const string& sSrcTxt, string& sDstTxt) { // 缓存大小 const size_t BUFFER_SIZE = sSrcTxt.size(); // 存放转换后的字符串缓存 char* szBuf = new char[BUFFER_SIZE]; // 原始字符串长度和地址 char* szIn = (char*)sSrcTxt.c_str(); size_t nInLen = sSrcTxt.size(); // 指向上面的缓存,在iconv调用中被改变 char* szOut = szBuf; size_t nOutLen = BUFFER_SIZE; do{ // 调用iconv转换字符串 size_t iRet = iconv(m_oCon, &szIn, &nInLen, &szOut, &nOutLen); // 将已转换的字符添加到输出字符串末尾 sDstTxt.append(szBuf, szOut - szBuf); // 判断异常条件 if (iRet == size_t(-1)) { if (EILSEQ == errno) { // 输入的字符串并不符合对应的编码规则 delete [] szBuf; throw runtime_error(string("Invalid input string : ") + sSrtTxt); } else if (EINVAL == errno) { // 输入的字符串不足够, delete [] szBuf; return szIn - sSrcTxt.c_str(); } else if (E2BIG == errno) { // 缓存空间不够 delete [] szBuf; szBuf = new char[BUFFER_SIZE]; szOut = szBuf; nOutLen = BUFFER_SIZE; } } // end of out-if } while(nInLen != 0); delete [] szBuf; return sSrcTxt.size(); }
参考文献
- 编码的故事: http://blog.csdn.net/iscandy/article/details/3859219
- Iconv简介:http://worldant.blog.sohu.com/96069463.html
完整的iconv C++ wrapper代码
MIConv.h
/** * @(#) MIConv.h 对iconv工具的封装 * * @author BourneLi * @version 1.0 * @history 2012-1-16 BourneLi 创建文件 */ #ifndef MICONV_H #define MICONV_H #include <string> #include <iconv.h> using namespace std; class MIConv { private: iconv_t m_oCon; string m_sFromCode; // 从编码m_sFromCode string m_sToCode; // 转向编码m_sToCode public: /** * 构造函数 * @param sFromCode 需要转换的原始编码 * @param sToCode 需要转换的目标编码 */ MIConv(const string& sFromCode, const string& sToCode); /** * 析构函数 */ ~MIConv(); /** * 转换文本 * @param sSrcTxt 需要转换的文本 * @param sDstTxt 目标编码 * @return 已经转换的字符串个数 */ size_t Convert(const string& sSrcTxt, string& sDstTxt); /** * gb2312转为utf8,适合较长文本 * @param sSrcTxt gb2312文本 * @param sDstTxt utf8文本 * @return 已经转换的字符串个数 */ static size_t GB2312ToUTF8(const string& sSrcTxt, string& sDstTxt); /** * gb2312转为utf8,适合较短文本 * @param sSrcTxt gb2312文本 * @return utf8文本 */ static string GB2312ToUTF8(const string& sSrcTxt); /** * utf8转为gb2312,适合较长文本 * @param sSrcTxt utf8文本 * @param sDstTxt gb2312文本 * @return 已经转换的字符串个数 */ static size_t UTF8ToGB2312(const string& sSrcTxt, string& sDstTxt); /** * utf8转为gb2312,适合较短文本 * @param sSrcTxt utf8文本 * @param sDstTxt gb2312文本 * @return 已经转换的字符串个数 */ static string UTF8ToGB2312(const string& sSrcTxt); }; #endif /* MICONV_H */
MIConv.cpp
/** * @(#) MIConv.cpp 对iconv工具的封装 * * @author BourneLi * @version 1.0 * @history 2012-1-16 BourneLi 创建文件 */ #include "MIConv.h" #include <stdexcept> #include <errno.h> /** * 构造函数 * @param sFromCode 需要转换的原始编码 * @param sToCode 需要转换的目标编码 */ MIConv::MIConv(const string& sFromCode, const string& sToCode): m_sFromCode(sFromCode), m_sToCode(sToCode) { m_oCon = iconv_open(sToCode.c_str(), sFromCode.c_str()); if (m_oCon == size_t(-1)) { throw runtime_error("iconv_open error"); } } /** * 析构函数 */ MIConv::~MIConv() { iconv_close(m_oCon); } /** * 转换文本 * @param sSrcTxt 需要转换的文本 * @param sDstTxt 目标编码 */ size_t MIConv::Convert(const string& sSrcTxt, string& sDstTxt) { // 缓存大小 const size_t BUFFER_SIZE = sSrcTxt.size(); // 存放转换后的字符串缓存 char* szBuf = new char[BUFFER_SIZE]; // 原始字符串长度和地址 char* szIn = (char*)sSrcTxt.c_str(); size_t nInLen = sSrcTxt.size(); // 指向上面的缓存,在iconv调用中被改变 char* szOut = szBuf; size_t nOutLen = BUFFER_SIZE; do{ // 调用iconv转换字符串 size_t iRet = iconv(m_oCon, &szIn, &nInLen, &szOut, &nOutLen); // 将已转换的字符添加到输出字符串末尾 sDstTxt.append(szBuf, szOut - szBuf); // 判断异常条件 if (iRet == size_t(-1)) { if (EILSEQ == errno) { // 输入的字符串并不符合对应的编码规则 delete [] szBuf; throw runtime_error(string("Invalid input string : ") + sSrtTxt); } else if (EINVAL == errno) { // 输入的字符串不足够, delete [] szBuf; return szIn - sSrcTxt.c_str(); } else if (E2BIG == errno) { // 缓存空间不够 delete [] szBuf; szBuf = new char[BUFFER_SIZE]; szOut = szBuf; nOutLen = BUFFER_SIZE; } } // end of out-if } while(nInLen != 0); delete [] szBuf; return sSrcTxt.size(); } /** * gb2312转为utf8 * @param sSrcTxt gb2312文本 * @param sDstTxt utf8文本 */ size_t MIConv::GB2312ToUTF8(const string& sSrcTxt, string& sDstTxt) { MIConv oConv("gb2312", "utf-8"); return oConv.Convert(sSrcTxt, sDstTxt); } /** * gb2312转为utf8,适合较短文本 * @param sSrcTxt gb2312文本 * @return utf8文本 */ string MIConv::GB2312ToUTF8(const string& sSrcTxt) { MIConv oConv("gb2312", "utf-8"); string sUTF8; oConv.Convert(sSrcTxt, sUTF8); return sUTF8; } /** * utf8转为gb2312 * @param sSrcTxt utf8文本 * @param sDstTxt gb2312文本 */ size_t MIConv::UTF8ToGB2312(const string& sSrcTxt, string& sDstTxt) { MIConv oConv("utf-8", "gb2312"); return oConv.Convert(sSrcTxt, sDstTxt); } /** * utf8转为gb2312,适合较短文本 * @param sSrcTxt utf8文本 * @param sDstTxt gb2312文本 * @return 已经转换的字符串个数 */ string MIConv::UTF8ToGB2312(const string& sSrcTxt) { MIConv oConv("utf-8", "gb2312"); string sGb2312; oConv.Convert(sSrcTxt, sGb2312); return sGb2312; }