• 网页中文乱码的那点事儿


    本文目的

    • 介绍工作中常见字符编码,主要涉及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;
    }
  • 相关阅读:
    统计数据持久化
    缓存层的实现
    C++语法疑点
    为什么需要定义虚的析构函数?
    C++ shared_ptr deleter的实现
    条件变量
    ubuntu  输入时弹出剪切板候选项
    leetcode Bitwise AND of Numbers Range
    C/C++ 字符串 null terminal
    C++ inline weak symbol and so on
  • 原文地址:https://www.cnblogs.com/bourneli/p/2325453.html
Copyright © 2020-2023  润新知