什么是字符集呢?
我們知道,在計算機內部,所有信息都是二進制表示的,二進制對于計算機來說是極為重要的,可以想象成一個開關的閉開信號,只有 0 1
兩種狀態。計算機中 8個二進制(8位
)代表一個字節,也就是說一個字節可以代表256種可能,于是上個世紀60年代,美國制定了一套字符集,對英語字符與二進制位之間的關系,做了統一規定。這被稱為ASCII
碼,一直沿用至今,ASCII碼一共規定了128個字符的編碼,比如空格"SPACE"是32(二進制00100000),大寫的字母A是65(二進制01000001)。這128個符號(包括32個不能打印出來的控制符號),只占用了一個字節的后面7位,最前面的1位統一規定為0。
英語用128個字符就夠了,但是世界上還有各種各樣的語言字符,如中文,就有上萬個。這樣子,即使是256個也不夠用,而且也無法統一。比如,一個二進制數可能會對應著各個國家不同的符號,這樣子我們打開一個文件,如果不知道這個文件的編碼方式,那就會亂碼,完全看不懂。
這樣子,Unicode
字符集就出現了,它將世界上所有的字符都納入其中了,在unicode
字符集中,每一個符號都對應著一個獨一無二的編碼,如4E25
表示漢字嚴
Unicode的問題
需要注意的是,Unicode只是一個符號集,它只規定了符號的二進制代碼,卻沒有規定這個二進制代碼應該如何存儲。
比如,漢字嚴
的unicode
是十六進制數4E25
,轉換成二進制數足足有15位(100111000100101),也就是說這個符號的表示至少需要2個字節。表示其他更大的符號,可能需要3個字節或者4個字節,甚至更多。
這里就有兩個嚴重的問題:
- 第一個問題是,如何才能區別
Unicode
和ASCII
?計算機怎么知道三個字節表示一個符號,而不是分別表示三個符號呢? - 第二個問題是,我們已經知道,英文字母只用一個字節表示就夠了,如果
Unicode
統一規定,每個符號用三個或四個字節表示,那么每個英文字母前都必然有二到三個字節是0,這對于存儲來說是極大的浪費,文本文件的大小會因此大出二三倍,這是無法接受的。
它們造成的結果是:
- 出現了
Unicode
的多種存儲方式,也就是說有許多種不同的二進制格式,可以用來表示Unicode。 -
Unicode
在很長一段時間內無法推廣,直到互聯網的出現。
編碼和字符集的區別
如上文所說,符號集只規定了符號的二進制代碼,卻沒有規定這個二進制代碼應該如何存儲,而編碼則可以說是指定字符集的具體實現方式,如 UTF-8
,UTF-16
就是Unicode
字符集的實現方式
UTF-8
互聯網的普及,強烈要求出現一種統一的編碼方式。UTF-8
就是在互聯網上使用最廣的一種Unicode
的實現方式。其他實現方式還包括UTF-16
(字符用兩個字節或四個字節表示)和UTF-32
(字符用四個字節表示),不過在互聯網上基本不用。重復一遍,這里的關系是,UTF-8
是Unicode
的實現方式之一。
UTF-8
最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度。
UTF-8
的編碼規則很簡單,只有二條:
- 對于單字節的符號,字節的第一位設為0,后面7位為這個符號的unicode碼。因此對于英語字母,UTF-8編碼和ASCII碼是相同的。
- 對于n字節的符號(n>1),第一個字節的前n位都設為1,第n+1位設為0,后面字節的前兩位一律設為10。剩下的沒有提及的二進制位,全部為這個符號的unicode碼。
下表總結了編碼規則,字母x表示可用編碼的位。
Unicode符號范圍(十六進制) | UTF-8編碼方式(二進制) |
---|---|
0000 0000-0000 007F | 0xxxxxxx |
0000 0080-0000 07FF | 110xxxxx 10xxxxxx |
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
跟據上表,解讀UTF-8編碼非常簡單。如果一個字節的第一位是0,則這個字節單獨就是一個字符;如果第一位是1,則連續有多少個1,就表示當前字符占用多少個字節。
下面,還是以漢字嚴
為例,演示如何實現UTF-8編碼。
已知嚴
的unicode是4E25(100111000100101)
,根據上表,可以發現4E25
處在第三行的范圍內(0000 0800-0000 FFFF
),因此嚴
的UTF-8編碼需要三個字節,即格式是1110xxxx 10xxxxxx 10xxxxxx
。然后,從嚴
的最后一個二進制位開始,依次從后向前填入格式中的x,多出的位補0。這樣就得到了,嚴
的UTF-8編碼是11100100 10111000 10100101
,轉換成十六進制就是E4B8A5
。
Little endian和Big endian
Unicode碼可以采用兩個字節格式直接存儲。以漢字嚴
為例,Unicode碼是4E25,需要用兩個字節存儲,一個字節是4E,另一個字節是25。存儲的時候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。
因此,第一個字節在前,就是"大頭方式"(Big endian)
,第二個字節在前就是"小頭方式"(Little endian)
。
那么很自然的,就會出現一個問題:計算機怎么知道某一個文件到底采用哪一種方式編碼?
Unicode規范中定義,每一個文件的最前面分別加入一個表示編碼順序的字符(BOM頭
),這個字符的名字叫做"零寬度非換行空格"(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。這正好是兩個字節,而且FF比FE大1。
如果一個文本文件的頭兩個字節是FE FF,就表示該文件采用大頭方式;如果頭兩個字節是FF FE,就表示該文件采用小頭方式。