网页中文乱码的那点事儿

  1. 云栖社区>
  2. 博客>
  3. 正文

网页中文乱码的那点事儿

吞吞吐吐的 2017-09-21 16:16:00 浏览783
展开阅读全文

本文目的

  • 介绍工作中常见字符编码,主要涉及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的代码就十分清楚了,下面是封装的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
 * @(#) 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/**
 * @(#) 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;
}
声明:如有转载本博文章,请注明出处。您的支持是我的动力!文章部分内容来自互联网,本人不负任何法律责任。
本文转自bourneli博客园博客,原文链接:http://www.cnblogs.com/bourneli/archive/2012/01/18/2325453.html,如需转载请自行联系原作者

网友评论

登录后评论
0/500
评论
吞吞吐吐的
+ 关注