网页中文乱码的那点事儿

本文目的

  • 介绍工作中常见字符编码,主要涉及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 转换模版:
[0000 - 007F]
0xxxxxxx
[0080 - 07FF]
110xxxxx 10xxxxxx
[0800 – FFFF]
1110xxxx 10xxxxxx 10xxxxxx

用于将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的设置与实际上的编码不一致导致,如图:

clip_image002

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结束后会出现下面四种情况:

  1. 全部解析成功,inbyteslef为0,返回转化次数(non-reversible conventions performaned during the call)。
  2. 缓存空间不够,返回“(size_t)-1”,errno设置为E2BIG
  3. 输入数据不完全,返回“(size_t)-1”,errno设置为EINVAL
  4. 输入的数据格式不正确,返回“(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();
}

 

参考文献

 

完整的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;
}

原文链接: https://www.cnblogs.com/bourneli/archive/2012/01/18/2325453.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    网页中文乱码的那点事儿

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/40432

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月8日 下午5:07
下一篇 2023年2月8日 下午5:08

相关推荐