本文目的
- 介绍工作中常见字符编码,主要涉及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.youkuaiyun.com/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;
}