網頁中文亂碼的那點事兒


本文目的

  • 介紹工作中常見字符編碼,主要涉及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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM