ASCII、GBK展运、Unicode 與 UTF-8
在計(jì)算機(jī)內(nèi)部活逆,所有信息最終都是一個(gè)二進(jìn)制值。每一個(gè)二進(jìn)制位(bit)有 0
和 1
兩種狀態(tài)拗胜,因此八個(gè)二進(jìn)制位就可以組合出 256 種狀態(tài)蔗候,這被稱為一個(gè)字節(jié)(byte)。也就是說埂软,一個(gè)字節(jié)一共可以用來表示 256 種不同的狀態(tài)锈遥,每一個(gè)狀態(tài)對(duì)應(yīng)一個(gè)符號(hào),就是 256 個(gè)符號(hào),從 00000000
到 11111111
所灸。
上個(gè)世紀(jì) 60 年代丽惶,美國(guó)制定了一套基于拉丁字母的計(jì)算機(jī)編碼系統(tǒng),用于顯示現(xiàn)代英文爬立。稱為 ASCII(American Standard Code for Information Interchange钾唬,美國(guó)信息交換標(biāo)準(zhǔn)代碼),一直沿用至今侠驯。
ASCII 一共規(guī)定了 128 個(gè)字符的編碼抡秆,在計(jì)算機(jī)中常用一個(gè)字節(jié)表示,字節(jié)最前面的一位統(tǒng)一規(guī)定為0
吟策,后面 7 位來表示具體的碼點(diǎn)(code point)儒士。值得一提的是在 ASCII 中碼點(diǎn)就是在 ASCII 字符集中的序號(hào),例如大寫的字母A
在 ASCII 字符集中對(duì)應(yīng)的二進(jìn)制是01000001
踊挠,而它的 ASCII 碼點(diǎn)為 65乍桂,剛好一一對(duì)應(yīng)。
雖然英語用 ASCII 編碼就夠了效床,但是對(duì)于其他語言睹酌,ASCII 是不夠的。例如漢字就多達(dá) 10 萬左右剩檀,因此中國(guó)政府就推出了 GB 2312(信息交換用漢字編碼字符集·基本集憋沿,國(guó)標(biāo)),其中主要包含了兩部分沪猴,即編碼字符集和編碼方式辐啄。具體細(xì)節(jié)這里就不贅述,但是簡(jiǎn)單來說只有 UTF-16 這種編碼方式的 Unicode运嗜。值得一提的是 GB 2312 本身的字符集標(biāo)準(zhǔn)理論上最多可以表示 256 x 256 = 65536 個(gè)字符壶辜,所以實(shí)際上目前我們常用的是GBK(《漢字內(nèi)碼擴(kuò)展規(guī)范(GBK)》1.0 版)這個(gè)字符集,不過 GBK 本身不是一個(gè)國(guó)標(biāo)担租,是微軟推出的一個(gè)擴(kuò)展(操作系統(tǒng)的發(fā)展遠(yuǎn)遠(yuǎn)超過國(guó)家制定標(biāo)準(zhǔn)的發(fā)展砸民,操作系統(tǒng)廠商不得不先解決人民的一個(gè)痛點(diǎn)),所以它并沒有后面的那個(gè)號(hào)奋救。
那么什么是剛才提到的 Unicode岭参?正如前面所說的中國(guó)政府推出了 GB 2312 字符集,那么其他國(guó)家尝艘、跨國(guó)公司自然也會(huì)推出自己的字符集演侯。如果我們把字符集想象成一個(gè)教室,每個(gè)課桌上坐的學(xué)生就是字符背亥,而每個(gè)學(xué)生的學(xué)號(hào)為碼點(diǎn)秒际,不難想象不同的教室會(huì)有各自給學(xué)生編學(xué)號(hào)的規(guī)則悬赏,同一個(gè)學(xué)生在不同的教室可能坐在不同的位置上,自然同一個(gè)學(xué)號(hào)在不同的教室找到的很有可能是不同的學(xué)生程癌。所以人們迫切需要一種規(guī)則舷嗡,可以把世界上所有的學(xué)生都放進(jìn)同一個(gè)教室,每個(gè)學(xué)生都有一個(gè)獨(dú)一無二的學(xué)號(hào)嵌莉,這樣就能方便的找到對(duì)應(yīng)的學(xué)生进萄,這就是 Unicode 字符集,就像它的名字都表示的锐峭,這是一種所有字符的字符集中鼠。
但是這樣又引出了一系列問題,首先 Unicode 作為一個(gè)獨(dú)立的機(jī)構(gòu)沿癞,希望能推動(dòng)全球文字編碼和字符集標(biāo)準(zhǔn)都統(tǒng)一援雇,但又不能廢除各地方性的編碼方案。Unicode 選擇創(chuàng)建了一套完全獨(dú)立標(biāo)記方式——Unicode scalar values椎扬,這個(gè)方案顯示與我們常見 ASCII 等內(nèi)碼數(shù)值方案完全不同惫搏,然后為了兼容其他主流方案,Unicode 推出了 Unicode 轉(zhuǎn)換格式(Unicode Transformation Format蚕涤,簡(jiǎn)稱為 UTF)筐赔,常見的有 UTF-8、UTF-16 和 UTF-32揖铜。其中 32 是一個(gè)固定四字節(jié)的編碼方案茴丰,他的碼點(diǎn)與 Unicode scalar values 是一一對(duì)應(yīng)的,比較漂亮天吓;16 是由雙字節(jié)和四字節(jié)切換的方案贿肩;8 是變長(zhǎng)的,單字節(jié)時(shí)兼容 ASCII龄寞。再者早期 Unicode 其實(shí)并沒有想到會(huì)進(jìn)來這么多的字符汰规,比如 ??????(家庭)這個(gè) emoji,由于種種原因人們不能滿足只由一個(gè)男人+女人+女孩/男孩這種形式的家庭物邑,不得不繼續(xù)加上 ??????(女人溜哮、女人、男孩)拂封,??????(女人茬射、女人鹦蠕、女孩)冒签,????????(女人、女人钟病、女孩萧恕、男孩)刚梭,???????? 家庭 (男人、男人票唆、女孩朴读、男孩),???????? 家庭 (男人走趋、男人衅金、女孩、女孩)……后來膚色也不能固定為白人簿煌,還得有黃種人氮唯,黑人,外星人之類的姨伟。
出于經(jīng)濟(jì)(能用 ASCII 表示的英文用 UTF-32 固定 4 字節(jié)的方案會(huì)占用額外的空間)和發(fā)展(當(dāng)然可能四字節(jié)也不一定能裝下越來越多的 Unicode 字符)的角度惩琉,UTF-8 目前成為了使用最廣的一種 Unicode 編碼方式。
UTF-8 規(guī)則
UTF-8 的編碼規(guī)則很簡(jiǎn)單夺荒,只有二條:
- 對(duì)于單字節(jié)的符號(hào)瞒渠,字節(jié)的第一位設(shè)為
0
,后面 7 位為這個(gè)符號(hào)的碼點(diǎn)技扼。因此對(duì)于英語字母伍玖,UTF-8 編碼和 ASCII 編碼是相同的。 - 對(duì)于
n
字節(jié)的符號(hào)(n > 1
)淮摔,第一個(gè)字節(jié)的前n
位都設(shè)為1
私沮,第n + 1
位設(shè)為0
,后面字節(jié)的前兩位一律設(shè)為10
和橙。剩下的沒有提及的二進(jìn)制位仔燕,全部為這個(gè)符號(hào)的碼點(diǎn)。
Unicode 和 UTF-8 之間的轉(zhuǎn)換關(guān)系表 ( x 字符表示碼點(diǎn)占據(jù)的位 )
碼點(diǎn)的位數(shù) | 碼點(diǎn)起值 | 碼點(diǎn)終值 | 字節(jié)序列 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 |
---|---|---|---|---|---|---|---|---|---|
7 | U+0000 | U+007F | 1 | 0xxxxxxx |
|||||
11 | U+0080 | U+07FF | 2 | 110xxxxx |
10xxxxxx |
||||
16 | U+0800 | U+FFFF | 3 | 1110xxxx |
10xxxxxx |
10xxxxxx |
|||
21 | U+10000 | U+1FFFFF | 4 | 11110xxx |
10xxxxxx |
10xxxxxx |
10xxxxxx |
||
26 | U+200000 | U+3FFFFFF | 5 | 111110xx |
10xxxxxx |
10xxxxxx |
10xxxxxx |
10xxxxxx |
|
31 | U+4000000 | U+7FFFFFFF | 6 | 1111110x |
10xxxxxx |
10xxxxxx |
10xxxxxx |
10xxxxxx |
10xxxxxx |
需要注意的問題
-
中文在 UTF-8 中并不一定長(zhǎng)三個(gè)字節(jié)
筆者經(jīng)常會(huì)看到一些經(jīng)驗(yàn)豐富的程序員會(huì)認(rèn)為一個(gè)中文字符在 GBK 中是兩個(gè)字節(jié)魔招,轉(zhuǎn)為 UTF-8 是三個(gè)字節(jié)晰搀。所以 UTF-8 中中文字符的長(zhǎng)度是三個(gè)字節(jié),實(shí)際上并不然办斑,需要看這個(gè)這個(gè)字符是不是在 Unicode 的基本面上外恕,非常見字可能會(huì)占 4 個(gè)字節(jié)(UTF-8 可能有 1~4 個(gè)字節(jié)),因?yàn)?GBK 標(biāo)準(zhǔn)提出的時(shí)間早乡翅,所以基本上都在 Unicode 的基本面上鳞疲。
-
計(jì)算 UTF-8 編碼的字符串長(zhǎng)度不要想當(dāng)然
由于 Cocos2d-x 原生并沒有提供計(jì)算 UTF-8 的 API,筆者見過很多奇思妙想的方式計(jì)算中英混合字符串長(zhǎng)度的方式蠕蚜。例如假設(shè)中英混合字符串的每個(gè)字符都占四個(gè)字節(jié)尚洽;調(diào)用原生 OC、Java 庫(kù)函數(shù) String 來計(jì)算長(zhǎng)度等靶累。但是得到長(zhǎng)度后可能需要截取字符串腺毫,截取多長(zhǎng)的參數(shù)又拿不準(zhǔn)癣疟。而且目前主流手機(jī)都支持輸入 emoji,當(dāng)玩家輸入的文字中有大量 emoji 時(shí)截取的效果就可能非常的不理想潮酒。
計(jì)算 UTF-8 編碼字符串長(zhǎng)度的實(shí)例
#include <iostream>
static inline size_t utf8Length(const char *s)
{
size_t i = 0, j = 0;
while (s[i])
{
//if ((s[i] & 0b11000000) != 0b10000000) j++;
if ((s[i] & 0xc0) != 0x80)
j++;
i++;
}
return j;
}
int main()
{
const auto &utf8 =
u8"蒼天有井獨(dú)自空睛挚,松柏孤島唯賞楓。武園枯藤空留蘭急黎,星落天川遙映瞳扎狱。";
auto size = utf8Length(utf8);
std::cout << size << std::endl;
return 0;
}
32
Process finished with exit code 0