之前,介紹了UTF-8和UTF-16,實際上還有UTF-32這個名稱就知道是什么意思了,因為應用不廣,所以就不展開說了。先說一個共同點:UTF-8和UTF-16都是變長編碼,因為UTF-16是變長編碼(每個字符為2字節或4字節)這事很少被提及。看標題應該也能猜到,我們主要說的是UTF-8和UTF-16的差別。但在說這個之前,我們要考慮,程序真正使用的編碼是什么?是Unicode嗎?實際上并不會真的使用Unicode(除非是UTF-32),而是使用UTF-8或UTF-16等(這就和GB2312編碼中真實使用的都是GB2312內碼一樣)。UTF-16雖然是變長,但實際上Unicode基本多文種面(0號面)的使用頻率非常高,這導致了UTF-6的編碼長度基本等于字符數的2倍。UTF-8編碼中,一個字符長度為1~4字節不等(Unicode目前最長為21位,所以不會使用介紹的5~6字節):
這樣實際上,UTF-8編碼的一個字符基本是1~3字節,具體的使用概率應該和用戶相關(就是用戶主要使用那種語言)。我們知道:Windows操作系統使用的是UTF-16編碼,Linux操作系統使用的是UTF-8編碼。這里分析一下它們各自的選擇邏輯,微軟是為了賣給全世界的,所以它選擇了UTF-16,例如,Java等也是使用了UTF-16。但Linux不同,它是開源的,早期的開發者都是歐美的,選擇UTF-8使用的空間一定不比UTF-16差(哪怕是拉丁字母占比更多,也就是和UTF-16持平,只有中文等字符占比多的情況下才會出現UTF-16更優的)。在使用的時候,有時需要UTF-8和UTF-16相互轉換,因為它們有規律,是可以相互轉換的。但是,如果需要GB18030和UTF-8相互轉換,就沒有可以終結的規律了。所以,這里就不介紹UTF-8和UTF-16相互轉換的代碼了,編碼的相互轉換可以使用iconv(參考附錄三)。在源碼的編碼上,推薦使用UTF-8,此時使用MSVC編譯,添加一個參數 /utf-8。傳輸協議的文本編碼方案,要優先考慮兼容性,然后再考慮節省空間。最后,說一下Windows上的編碼問題,內核使用的是UTF-16編碼,但很多API,都提供了A版和W版,例如,CreateFile這個API,實際上提供了CreateFileA和CreateFileW兩個版本,而CreateFile實際上是一個宏。但是有個別API只有W版的,更有甚者,W版和A版的功能(或效果)不完全一致,所以,如果可以,盡量使用W版本的。Windows的應用程序主要有兩種(Console和Windows,VS界面設置里在Linker->SubSystem中)。Console的默認編碼通常并不是UTF-16,中文系統默認為代碼頁936(這個可以看之前的文章,代碼頁、GB2312或GBK等),這個默認編碼通常稱為本地編碼。本地編碼和UTF-16的相互轉化可以使用下面的系統API:
注意,這兩個API的用法和iconv差異挺大的,微軟的API文檔很完整,這里就不展開了。非必要,Windows上不用iconv,這個看附錄三。Linux的編碼使用UTF-8的一個好處,就是內核裁剪上有那么一點點優勢----就是剪裁到嵌入式系統的層度。關于信創系統的編碼,目前基本都是UTF-8編碼。我能想到的兩個原因:1. 修改系統層面的編碼節省的空間和現在計算機硬件相比,實際上已經沒那么重要了2. 能夠更大層度的兼容當前的軟件。這里的兼容主要是指二進制的兼容,就是第三方軟件不需要重新編譯就可以運行。關于iconv這個庫,需要注意函數iconv,一共5個參數,第二和第三兩個參數共同組成一個內存段(內存起點指針和長度),第四和第五兩個參數共同組成一個內存段。它對內存段的使用有一點特別,都是傳入傳出參數(即是入參也是出參)。無論函數是否成功,內存段都會變為剩余的部分,舉例,下面示例代碼:// 示例代碼1
#include <iconv.h>
int main()
{
// 編碼轉換的源編碼文本
constchar utf8[] = "幻想";
// 轉換后的編碼的儲存位置
char buff[20] = { 0 };
// in_size值是7, 如果使用strlen則是6,都可以
size_t in_size = sizeof(utf8);
// 這里是 UTF-8 轉 GB2312
iconv_t co = iconv_open( "GB2312", "UTF-8");
do {
if (co == (iconv_t)(-1)) {
// iconv_open失敗
break;
}
// 這里一定要使用一個新的指針變量,iconv函數不能直接使用變量 utf8
// 因為 utf8 作為一個指針,是不能修改的,這不符合iconv的用法
char* in_buff = (char*)(utf8);
// 輸出內存段的起始位置
size_t out_size = sizeof(buff);
// 這里也需要使用一個新的指針變量
char* out_buff = (char*)(buff);
// 在iconv執行前:
// in_buff == utf8 而且 out_buff == buff
size_t result = iconv(co, &in_buff, &in_size, &out_buff, &out_size);
// 在iconv執行前:
// in_buff != utf8 而且 out_buff != buff
// 通常為 in_buff > utf8 而且 out_buff > buff
// 并且 in_size 和 out_size 的值也會變小
// -------------------
// GB2312的編碼結果存在的內存起點為 buff, 長度為 sizeof(buff) - out_size
// result == (iconv_t)(-1) 為失敗
} while(false);
if (co != (iconv_t)(-1)) {
// 這里回收 iconv_open 的資源
iconv_close(co);
}
}
在linux和mac上,基本都是默認安裝了iconv庫的。如果在Windows上需要使用iconv,可以參考下面文檔:
閱讀原文:原文鏈接
該文章在 2025/4/21 10:32:56 編輯過