C++讀取UTF-8及GBK系列的文字方法及原理

jostree發表於2015-03-28

1.讀取UTF-8編碼文字原理

首先了解UTF-8的編碼方式,UTF-8採用可變長編碼的方式,一個字元可佔1位元組-6位元組,其中每個字元所佔的位元組數由字元開始的1的個數確定,具體的編碼方式如下:

U-00000000 – U-0000007F: 0xxxxxxx
U-00000080 – U-000007FF: 110xxxxx 10xxxxxx
U-00000800 – U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
U-00010000 – U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U-00200000 – U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 – U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

因此,對於每個位元組如果起始位為“0”則說明,該字元佔有1位元組。

如果起始位為“10”則說明該位元組不是字元的其實位元組。

如果起始為為n個“1”+1個“0”,則說明改字元佔有n個位元組。其中1≤n≤6。

因此對於UTF-8的編碼,我們只需要每次計算每個字元開始位元組的1的個數,就可以確定這個字元的長度。

2.讀取GBK系列文字原理

對於ASCII、GB2312、GBK到GB18030編碼方法是向下相容的 ,即同一個字元在這些方案中總是有相同的編碼,後面的標準支援更多的字元。

在這些編碼中,英文和中文可以統一地處理。區分中文編碼的方法是高位元組的最高位不為0。

因此我們只需處理好GB18130,就可以處理與他相容的所有編碼,對於GB18130使用雙位元組變長編碼。

單位元組部分從 0×0~0x7F 與 ASCII 編碼相容。雙位元組部分,首位元組從 0×81~0xFE,尾位元組從 0×40~0x7E以及 0×80~0xFE,與GBK標準基本相容。

因此只需檢測首位元組是否小於0×81即可確定其為單位元組編碼還是雙位元組編碼。

3.C++程式碼實現

對於一個語言處理系統,讀取不同編碼的文字應該是最基礎的需求,文字的編碼方式應該對系統其他呼叫者透明,只需每次獲取一個字元即可,而不需要關注這個文字的編碼方式。從而我們定義了抽象類Text,及其介面ReadOneChar,並使兩個文字類GbkText和UtfText繼承這個抽象類,當系統需要讀取更多種編碼的檔案時,只需要定義新的類然後繼承該抽象類即可,並不需要更改呼叫該類的程式碼。從而獲得更好的擴充套件性。

更好的方式是使用簡單工廠模式,使不同的文字編碼格式對於呼叫類完全透明,簡單工廠模式詳解請參看:C++實現設計模式之 — 簡單工廠模式

其中Text抽象類的定義如下:

#ifndef TEXT_H
#define TEXT_H
#include <iostream>
#include <fstream>
using namespace std;
class Text
{
    protected:
        char * m_binaryStr;
        size_t m_length;
        size_t m_index;
    public:
        Text(string path);
        void SetIndex(size_t index);
        virtual bool ReadOneChar(string &oneChar) = 0;
        size_t Size();
        virtual ~Text();
};
#endif

Text抽象類的實現如下:

#include "Text.h"
using namespace std;
Text::Text(string path):m_index(0)
{
    filebuf *pbuf;
    ifstream filestr;
    // 採用二進位制開啟 
    filestr.open(path.c_str(), ios::binary);
    if(!filestr)
    {
        cerr<<path<<" Load text error."<<endl;
        return;
    }
    // 獲取filestr對應buffer物件的指標 
    pbuf=filestr.rdbuf();
    // 呼叫buffer物件方法獲取檔案大小
    m_length=(int)pbuf->pubseekoff(0,ios::end,ios::in);
    pbuf->pubseekpos(0,ios::in);
    // 分配記憶體空間
    m_binaryStr = new char[m_length+1];
    // 獲取檔案內容
    pbuf->sgetn(m_binaryStr,m_length);
    //關閉檔案
    filestr.close();
}

void Text::SetIndex(size_t index)
{
    m_index = index;
}

size_t Text::Size()
{
    return m_length;
}

Text::~Text()
{
    delete [] m_binaryStr;
}

GBKText類的定義如下:

#ifndef GBKTEXT_H
#define GBKTEXT_H
#include <iostream>
#include <string>
#include "Text.h"
using namespace std;
class GbkText:public Text
{
public:
    GbkText(string path);
    ~GbkText(void);
    bool ReadOneChar(string & oneChar);
};
#endif

GBKText類的實現如下:

#include "GbkText.h"
GbkText::GbkText(string path):Text(path){}
GbkText::~GbkText(void) {}
bool GbkText::ReadOneChar(string & oneChar)
{
    // return true 表示讀取成功,
    // return false 表示已經讀取到流末尾
    if(m_length == m_index)
        return false;
        if((unsigned char)m_binaryStr[m_index] < 0x81)
    {
        oneChar = m_binaryStr[m_index];
        m_index++;
    }
    else
    {
        oneChar = string(m_binaryStr, 2);
        m_index += 2;
    }
    return true;
}

UtfText類的定義如下:

#ifndef UTFTEXT_H
#define UTFTEXT_H
#include <iostream>
#include <string>
#include "Text.h"
using namespace std;
class UtfText:public Text
{
public:
    UtfText(string path);
    ~UtfText(void);
    bool ReadOneChar(string & oneChar);
private:
    size_t get_utf8_char_len(const char & byte);
};
#endif

UtfText類的實現如下:

#include "UtfText.h"
UtfText::UtfText(string path):Text(path){}
UtfText::~UtfText(void) {}
bool UtfText::ReadOneChar(string & oneChar)
{
    // return true 表示讀取成功,
    // return false 表示已經讀取到流末尾
    if(m_length == m_index)
        return false;
    size_t utf8_char_len = get_utf8_char_len(m_binaryStr[m_index]);
    if( 0 == utf8_char_len )
    {
            oneChar = "";
            m_index++;
        return true;
    }
    size_t next_idx = m_index + utf8_char_len;
    if( m_length < next_idx )
    {
        //cerr << "Get utf8 first byte out of input src string." << endl;
        next_idx = m_length;
    }
    //輸出UTF-8的一個字元
    oneChar = string(m_binaryStr + m_index, next_idx - m_index);
    //重置偏移量
    m_index = next_idx;
    return true;
}

size_t UtfText::get_utf8_char_len(const char & byte)
{
    // return 0 表示錯誤
    // return 1-6 表示正確值
    // 不會 return 其他值 

    //UTF8 編碼格式:
    //     U-00000000 - U-0000007F: 0xxxxxxx  
    //     U-00000080 - U-000007FF: 110xxxxx 10xxxxxx  
    //     U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx  
    //     U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx  
    //     U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx  
    //     U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx  

    size_t len = 0;
    unsigned char mask = 0x80;
    while( byte & mask )
    {
        len++;
        if( len > 6 )
        {
            //cerr << "The mask get len is over 6." << endl;
            return 0;
        }
        mask >>= 1;
    }
    if( 0 == len)
    {
        return 1;
    }
    return len;
}

工廠類TextFactory的類定義如下:

#ifndef TEXTFACTORY_H
#define TEXTFACTORY_H
#include <iostream>
#include "Text.h"
#include "UtfText.h"
#include "GbkText.h"
using namespace std;
class TextFactory
{
    public:
        static Text * CreateText(string textCode, string path);
};
#endif

工廠類的實現如下:

#include "TextFactory.h"
#include "Text.h"
Text * TextFactory::CreateText(string textCode, string path)
{
    if( (textCode == "utf-8") 
                || (textCode == "UTF-8") 
                || (textCode == "ISO-8859-2")
                || (textCode == "ascii") 
                || (textCode == "ASCII")
                || (textCode == "TIS-620")
                || (textCode == "ISO-8859-5") 
                || (textCode == "ISO-8859-7") ) 
    {
        return new UtfText(path);
    }
    else if((textCode == "windows-1252") 
                || (textCode == "Big5")
                || (textCode == "EUC-KR") 
                || (textCode == "GB2312") 
                || (textCode == "ISO-2022-CN") 
                || (textCode == "HZ-GB-2312") 
                || (textCode == "gb18030"))
    {
        return new GbkText(path);
    }
    return NULL;
}

測試的Main函式如下:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include "Text.h"
#include "TextFactory.h"
#include "CodeDetector.h"
using namespace std;
int main(int argc, char *argv[])
{
    string path ="日文"; 
    string code ="utf-8";
    Text * t = TextFactory::CreateText(code, path);
    string s;
    while(t->ReadOneChar(s))
    {
        cout<<s;
    }
    delete t;
}

相關文章