轉自 http://blog.csdn.net/langresser_king/article/details/7459367
iconv(http://www.gnu.org/software/libiconv/)是一個開源的字元編碼轉換庫,可以“方便”的完成幾乎所有的編碼轉換工作。說簡單是因為,它常用的介面就三個,iconv_open iconv iconv_close,但是即便是隻有三個介面,要想使用正確也不容易。這裡把一些基本概念和使用細節記錄下來,希望能成為一篇最實用的入門教程。
一、字元編碼基本概念
更詳細的內容可以參考百度百科(http://baike.baidu.com/view/1204863.htm),或是自行google。這裡會記錄最核心的幾個概念。
1、ASCII編碼,就是英文顯示文字所需要的256個字元(比如,英文字母、數字、標點符號等等)
2、ANSI編碼,像中文,肯定不能只用256個字元就代表所有漢字。因此對ASCII碼錶進行了擴充套件,使用兩個(或多個)位元組,代表一個漢字。類似的,不同的國家和地區制定了不同的標準,這些使用 2 個位元組來代表一個字元的各種延伸編碼方式,稱為 ANSI 編碼。也就是說,ANSI是一種對ASCII碼錶進行擴充套件的泛稱,不同語言作業系統,其代表的編碼方式不一樣。比如中文作業系統,ANSI編碼就代指GB2312;日文作業系統ANSI編碼就代指JIS。
3、Unicode編碼,Unicode是一個超大的集合,也是一個統一的標準,可以容納世界上的所有語言符號。每個符號的編碼都不一樣,比如,U+0639表示阿拉伯字母Ain,U+0041表示英語的大寫字母A,“漢”這個字的Unicode編碼是U+6C49。
4、內碼表(codepage),Unicode是一個世界統一的標準,也就是說,如果一個文字是用Unicode方式編碼的,那麼它可以同時顯示中文、日文、阿拉伯文等等,並且是在任何系統上都可以正常顯示的。但是由於ANSI編碼之間互不相容,因此就需要有一個標識來表明不同的ANSI編碼到Unicode之間的對映關係(也就是不同編碼之間的對映關係),這個就是內碼表。比如簡體中文的內碼表是CP_936(中文系統預設的內碼表),這個也就是windows API中MultiByteToWideChar第一個引數所代表的含義。如果不標明內碼表,系統是不知道如何進行編碼轉換的。
5、SBCS(單位元組字符集),MBCS(多位元組字符集),DBCS(寬位元組字符集),分別對應上面提到的ASCII編碼、ANSI編碼、Unicode編碼。
6、中文常見編碼:GB2312(CP_20936)->GBK(CP_936)->GB18030(CP_54936),三種編碼方式向下相容,也就是說GB18030包含GB2312的所有字元。GB18030在2000年取代GBK成為正式國家標準。
7、UCS(Unicode Character Set):UCS-2規定了2個位元組代表一個文字,還有UCS-4規定了4個位元組代表一個文字。我們工作中幾乎總是在和UCS-2打交道。
8、UTF(UCS Transformation Format):UCS只是規定的如何編碼,但是沒有規定如何傳輸、儲存這個編碼。UTF則規定了由幾個位元組儲存這個編碼。UTF-7,UTF-8,UTF-16都是比較常見的編碼方式。UTF-8編碼與Unicode編碼並不相同,但是它們之間可以通過計算進行轉換,而不像ANSI和Unicode之間必須通過一個對映表來人為規定其對應關係。UTF-16完全對應於UCS-2,並可通過計算代表一部分UCS-4文字。還有UTF-32則是完全對應於UCS-4,不過很不常見就是了。
9、UTF-8是與ASCII碼相容的,英文字母1個位元組,漢字通常是3個位元組;UTF-16的所有字元都是用2個位元組進行儲存,其編碼與Unicode是等價的。UTF-16又分為UTF-16LE(little endian)和UTF-16BE(big endian),比如一個字母'a',如果按utf-8來存,就是0x61;如果按utf-16le來存就是0x61 0x00(低有效位在前);如果按utf-16be來存就是0x00 0x61(高有效位在前)。這個也就是我們用記事本另存檔案的時候可以選擇的幾個編碼方式的含義。
10、BOM(byte order mark),上面提到的utf-8 utf-16le utf16-be都是unicode編碼,但是系統依然無法正確解析一個文字檔案,即便已經知道它是unicode編碼。所以就有了這樣的規定:在文字檔案的最開頭插入幾個位元組的標識,來說明編碼方式。utf-8的BOM是0xef 0xbb 0xbf,utf-16le的BOM是0xff 0xfe,utf16-be的BOM是0xfe 0xff。事實上BOM並不是必須的,它僅僅是幫助程式自動判斷編碼方式使用的,如果我們手動選擇編碼方式(像ANSI一樣),即便沒有BOM,也是可以正常顯示的。反過來說,程式讀文字檔案的時候要先讀文字開始的三個位元組判斷下編碼方式。
二、iconv支援的編碼格式:
- European languages
- ASCII, ISO-8859-{1,2,3,4,5,7,9,10,13,14,15,16}, KOI8-R, KOI8-U, KOI8-RU, CP{1250,1251,1252,1253,1254,1257}, CP{850,866,1131}, Mac{Roman,CentralEurope,Iceland,Croatian,Romania}, Mac{Cyrillic,Ukraine,Greek,Turkish}, Macintosh
- Semitic languages
- ISO-8859-{6,8}, CP{1255,1256}, CP862, Mac{Hebrew,Arabic}
- Japanese
- EUC-JP, SHIFT_JIS, CP932, ISO-2022-JP, ISO-2022-JP-2, ISO-2022-JP-1
- Chinese
- EUC-CN, HZ, GBK, CP936, GB18030, EUC-TW, BIG5, CP950, BIG5-HKSCS, BIG5-HKSCS:2004, BIG5-HKSCS:2001, BIG5-HKSCS:1999, ISO-2022-CN, ISO-2022-CN-EXT
- Korean
- EUC-KR, CP949, ISO-2022-KR, JOHAB
- Armenian
- ARMSCII-8
- Georgian
- Georgian-Academy, Georgian-PS
- Tajik
- KOI8-T
- Kazakh
- PT154, RK1048
- Thai
- ISO-8859-11, TIS-620, CP874, MacThai
- Laotian
- MuleLao-1, CP1133
- Vietnamese
- VISCII, TCVN, CP1258
- Platform specifics
- HP-ROMAN8, NEXTSTEP
- Full Unicode
- UTF-8
UCS-2, UCS-2BE, UCS-2LE
UCS-4, UCS-4BE, UCS-4LE
UTF-16, UTF-16BE, UTF-16LE
UTF-32, UTF-32BE, UTF-32LE
UTF-7
C99, JAVA - Full Unicode, in terms of
uint16_t
oruint32_t
(with machine dependent endianness and alignment) - UCS-2-INTERNAL, UCS-4-INTERNAL
- Locale dependent, in terms of `char' or `wchar_t' (with machine dependent endianness and alignment, and with OS and locale dependent semantics)
- char, wchar_t
The empty encoding name "" is equivalent to "char": it denotes the locale dependent character encoding.
When configured with the option --enable-extra-encodings
, it also provides support for a few extra encodings:
- European languages
- CP{437,737,775,852,853,855,857,858,860,861,863,865,869,1125}
- Semitic languages
- CP864
- Japanese
- EUC-JISX0213, Shift_JISX0213, ISO-2022-JP-3
- Chinese
- BIG5-2003 (experimental)
- Turkmen
- TDS565
- Platform specifics
- ATARIST, RISCOS-LATIN1
通過第一部分的講解,這些編碼格式應該看著比較清晰了。比如gb2312-->unicode的轉化就是GBK(或者是gb18030 cp936,我們之前說過,大多數情況這些是等價的)到ucs-2(或者是utf-16,如果文字資訊中沒有BOM就要特別指定utf-16le或是utf-16be)的轉化。這些就是我們將要用到的編碼轉換的引數。
三、iconv函式詳解
1、iconv_t iconv_open (const char* tocode, const char* fromcode);
如果轉換編碼不支援(通常是寫錯了),那麼就返回-1,否則返回一個控制程式碼。tocode和fromcode傳的就是上面列表中的引數。補充,如果在tocode後面追加"//TRANSLIT"(比如"utf-8//TRANSLIT"),那麼如果一個字元無法被轉換,則會自動尋找相似字元進行替換。如果追加的是"//IGNORE",則會忽略無法轉換的字元。
2、size_t iconv (iconv_t cd,
const char* * inbuf, size_t * inbytesleft,
char* * outbuf, size_t * outbytesleft);
真正用於轉換的函式,cd就是iconv_open返回的控制程式碼,要注意,iconv會修改傳入的引數,所以要儲存好原始outbuf指標。轉換完畢後inbuf會指向無法成功轉換而被截斷的第一個字元,inbutesleft顧名思義就是有多少字元尚未轉換,如果全部轉換成功當然就是0了,outbuf指向輸出快取的轉換後的字元的末尾,outbutesleft表明輸出快取尚有多少自己剩餘。
這個函式有很多細節需要注意。如果inbuf中遇到非法位元組序列會截斷,這時inbuf就指向被截斷的第一個位元組。這種情況一般出現在編碼指定錯誤或者是資料來源被截斷的時候。比如我們指定gb2312轉ut-8,但是資料來源裡出現阿拉伯字元,這個時候就會發生截斷。 如果outbuf空間不足,也會發生截斷,不過這種情況相對少見,因為我們程式中會保證輸出快取有足夠空間。
另一種情況相對比較“正常”,就是被轉的字符集不包含源字符集的字元,比如utf-8到gb2312的轉換就很有可能發生這種情況。這時tocode的追加引數就起作用了,iconv會自動進行替換或者忽略。
如果轉換成功(沒有發生截斷),iconv返回的是不可逆的字元總數(也就是被替換或是忽略的字元總數,如果一切正常,應該返回0),如果轉換失敗,返回-1.
3、int iconv_close (iconv_t cd);
釋放控制程式碼資源。
四、utf-8-->gb18030轉換示例
這個代表了ansi和utf-8或者是ansi之間互相轉換的程式碼寫法。
- <span style="font-size:16px;">iconv_t cd = iconv_open("gb18030//TRANSLIT", "<span style="font-family: Georgia; ">utf-8</span>");
- const char* inbuffer; // 輸入源,要轉換的字串
- int srcLen = strlen(inbuffer);
- int outLen = 1024;
- static char s_outbuffer[outLen];
- memset(s_outbuffer, 0, outLen);
- const char* srcStart = inbuffer;
- char* tempOutBuffer = s_outbuffer;//要有這個臨時變數,否則iconv會直接改寫s_outbuffer指標
- size_t ret = iconv(<span style="font-family:Georgia;">cd</span>, (const char**)&srcStart, (size_t *)&srcLen, &tempOutBuffer, (size_t *)&outLen);
- iconv_close(cd);
- </span>
五、unicode->gb18030轉換示例
這個代表了寬字元和多位元組之間的轉換,也就是wchar_t和char之間的轉換,也就是WideCharToMultiByte所完成的操作。
- FILE* fp = fopen("test-utf16be.txt", "rb"); // 檔案是unicode檔案,這裡要用"rb"來讀取,否則有些位元組等同於EOF檔案會被截斷
- char srcBuffer[1024] = {0};
- fread(srcBuffer, sizeof(srcBuffer) - 1, 1, fp);
- // 讀取BOM資訊
- // char x[2];
- // fread(&x[0], 1, 1, fp);
- // fread(&x[1], 1, 1, fp);
- // 帶有BOM資訊,直接用"utf-16";否則應該特別指定"utf-16le"或是"utf-16be",這個要區分清楚,否則會出現亂碼
- iconv_t cd = iconv_open("gb18030//TRANSLIT", "utf-16");
- int srcLen = 1024;<span style="white-space:pre"> </span>// srcLen和outLen代表的都是位元組數,如果是wchar_t就是字元數*sizeof(wchar_t)
- int outLen = 1024;
- static char s_outbuffer[outLen];
- memset(s_outbuffer, 0, outLen);
- const char* srcStart = (const char*)srcBuffer;// srcBuffer同樣可以是wchar_t,這裡也是這樣強制轉換
- char* tempOutBuffer = s_outbuffer;//要有這個臨時變數,否則iconv會直接改寫s_outbuffer指標
- size_t ret = iconv(cd, (const char**)&srcStart, (size_t *)&srcLen, &tempOutBuffer, (size_t *)&outLen);
- iconv_close(cd);