編碼含義
關於編碼的含義,之前也說過,計算機只能存儲二進制序列
所以對於字符,保存的時候,需要進行編碼為二進制,進行存儲
呈現的時候,需要將二進制進行解碼,轉換成字符的形式
有很多種編碼方式,比如ASCII
(American Standard Code for Information Interchange,美國信息交換標准代碼)
使用一個字節進行編碼,一個字節可以表示的最大值為255
很顯然,對於英語和其他一些西歐語言來說,足夠了,英文字母總共才幾個對吧
那么對於漢字呢?ASCII顯然是不夠用的,所以出現了GBK等以支持漢字
那么,日語 韓語呢?他們當然也搞出來一些支持他們文字的編碼
Unicode誕生
於是,人們意識到他們應該提出一種標准方案來展示世界上所有語言中的所有字符,出於這個目的,Unicode誕生了
Unicode 是一種字符集,規定了符號對應的二進制代碼
至於這個二進制代碼如何存儲則沒有任何規定,也就是說它是一種編碼規定
是編碼字符集,而不是實際的編碼方案
最初Unicode使用一個16位長度的二進制序列,也就是最多支持 (2的16次方-1) 65536個字符,也就是0 ~ 65535
范圍是 0x0000 ~0xFFFF
Unicode使用U+前綴, 加上編碼的值,來表示Unicode中的字符編碼
也就是 U+0000 ~ U+FFFF
Unicode不夠用了
顯然,隨着更多字符的增加, 65536 是不夠用的
於是Unicode 不得不進行擴展,於是使用8位用作擴展位,形式如下
一個字節8位可以表示 2的8次方-1 = 256 個數,最大可以擴展為 256 *65526=16777216 個
不過也不需要那么多的字符, 僅僅使用了前面的17個
也就是
00 01 02 03 04 05 06 07 08 0A 0B 0C 0D 0E 0F 10 |
於是按照擴展位,划分了17個維度,這每個維度,叫做一個平面
17個平面,編號從 0~16
|
每個平面 65536個字符 |
17個平面,擴展后總共可以表示1114112個字符 |
擴展后的范圍為
U+000000 ~ U+10FFFF
|
其中對於第一個U+00 也就是U+0000到U+FFFF ,包含了最常見的字符
被稱作 基本多語言平面 Basic Multilingual Plane, BMP
其余的U+010000 ~ U+10FFFF 被稱作輔助平面
至此你應該了解到了,什么是Unicode的
他就是一個具有17個平面,每個平面可以容納65536個字符的一張巨大的字符碼表
一個字符對應一個二進制序列
基本平面中使用四位十六進制數 在零號平面以外的字符則需要使用五位或六位十六進制數了
編碼方式
Unicode 沒有規定字符對應的二進制碼在計算機中如何存儲,只是規定了他的值是多少而已
一個字符對應的實際的值,我們稱之為代碼點 code point
那么一個碼點實際的值怎么存儲呢?
可能需要1個,可能需要2個,甚至可能需要3個或者4個字節來表示
對於計算機來說,面對着一堆字節,他們知道到底哪個或者哪幾個是一個字符呢?
聽起來可能有點迷惑,不是知道具體的值了么?怎么還不知道如何表示? 比如數字1 他的碼點是1 假如我用兩個字節來存儲,每個字節的前兩位我當做其他的標志位, 設置為11 那么可能結果是這樣子的11000000 11000001 顯然,他的值並不是1 編碼方式只是可以保證,你的字符是按照指定的字符集進行編碼的 也就是說如果你告訴我拿出來碼點為1 的,我會把1100 0000 1100 0001 解析成數字1 但是並不能保證我保存的數據就是他的碼點的真值,0000 0001 ,中間形式是編碼方式說了算的 最直觀的例子就是網絡中報文的傳輸,都會附加自己的頭信息 所以中間傳輸的數據並不是跟你發送的數據一模一樣,中間的數據就是編碼形式的存儲 但是,接收端接受解析后,就是跟你發送的數據一樣的,這就好像是你的字符 |
存儲的問題就是編碼方式的問題,就是表示成什么形式的問題
編碼方式有UTF-8 UTF-16 UTF-32
UTF-8 是變長
UTF-32 是定長
UTF-16介於他們之間 2個字節或者4個字節
|
utf-16
UTF-16編碼以16位無符號整數為單位 |
我們把Unicode編碼記作U 編碼規則如下
如果U<0x010000, 也就是0x000000 ~ 0x00FFFF
U的UTF-16編碼, 就是U對應的16位無符號整數
|
如果U≥0x010000 也就是0x010000 ~ 0x10FFFF 我們先計算下 U'=U-0x010000 可以得出來 U' 范圍是 0x000000 ~ 0x0FFFFF 顯然, U'的最大值為0xFFFFF 也就是最多20個1 也就是可以被寫成20個二進制位 既然是20個二進制位,那么我們是不是可以把它拆分成兩組呢? 每組10個二進制位 00 0000 0000 它能表示的范圍是2的10次方=1024個 BMP是2個字節,16位, 很顯然,如果把U' 拆分成兩組,每組10個二進制位的話 每一個都能夠保存到2個字節內 所以Unicode標准規定:基本多語言平面內,U+D800..U+DFFF的值不對應於任何字符,為代理區 ,其中又分為高代理區和低代理區 U+D800 加上10個二進制位的數值的最大值,可以得到高代理區的范圍 U+D800 --->1101 10 00 0000 0000 + 0000 00 11 1111 1111 = 1101 1011 1111 1111 = 0xDBFF 下一個就是0xDBFF +1 = 0xDC00,所以低代理區從0xDC00 開始 0xDC00 加上10個二進制位的數值的最大值,可以得到低代理區的范圍 0xDC00----> 1101 1100 0000 0000 + 0000 00 11 1111 1111 = 1101111111111111 = 0xDFFF |
高代理區范圍 U+D800 ~0xDBFF 低代理區范圍 0xDC00 ~ 0xDFFF 代理區間是U+D800....U+DFFF |
所以UTF-16的編碼方式就是 先計算 U'=U-0x010000 然后將U'寫成二進制形式:yyyy yyyy yyxx xxxx xxxx 然后分別計算高位代理和低位代理 U+D800 --->1101 10 00 0000 0000 + 0000 00 yy yyyy yyyy = 1101 10 yy yyyy yyyy 0xDC00----> 1101 1100 0000 0000 + 0000 00 xx xxxx xxxx = 1101 11xx xxxx xxxx |
再精簡下步驟 1. 先計算 U'=U-0x010000 2. 然后將U'寫成二進制形式:yyyy yyyy yyxx xxxx xxxx 3.兩個值為 1101 10 yy yyyy yyyy / 1101 11xx xxxx xxxx |
在這種處理方式下
如果一旦讀取到值為U+D800 ~0xDBFF ,他必然是代理區間中的一部分,也就非 0號平面中的字符
而且,我們還能夠根據值判斷出來,到底是高位還是低位,也就是第一個還是第二個數值
只需要取出來這20位,然后再加上0x010000 這就是這個字符的碼點
可以看得出來,對於基本平面中的字符,使用2個字節長度,16位表示,這被稱之為一個代碼單元
對於除了基本平面外的輔助平面,使用4個字節長度來表示,也就是兩個代碼單元
之前我們提到過,Unicode中的一個字符的值,被稱之為一個碼點
顯然,一個碼點,可能被一個代碼單元存儲,也可能被兩個連續的代碼單元存儲
|
UTF-32
UTF-32編碼以32位無符號整數為單位
Unicode的UTF-32編碼就是其對應的32位無符號整數
32位可以表示的個數為 2的32次方 為4294967296,顯然綽綽有余,沒什么好說的了
UTF-8
UTF-8 是目前互聯網上使用最廣泛的一種 Unicode 編碼方式,可變長
使用 1 - 4 個字節表示一個字符,根據字符的不同變換長度
字節序
根據我們上面描述的utf8 以及utf16都有可能使用不止一個字節進行編碼
其實還有很多其他數據也不僅僅是一個字節進行表達
在計算機中最終都是二進制序列的形式
比如utf-16中,雖然我可以根據值確定是否在0號平面內,還是在擴展輔助平面的
但是,如何把一個二進制序列解析為他的值,這是一個問題
比如
0000 0001 0000 0010 假如說這是一個十六進制數
他到底表示的是0102 還是0201? 從哪邊開始解讀?
人會很自然的把左邊當高位,右邊當低位, 但是, 計算機, 你必須對他進行說明
這就是字節序的問題
其實就是高位和低位與內存地址高低的對應關系
分為大端排序( Big endian BE)和小端排序(Little endian LE)
大端排序----高地址存儲低位 低地址存儲高位
小端排序----高地址存儲高位 低地址存儲低位
在內存中0x01020304的存儲方式
內存地址 4000 4001 4002 4003
BE 01 02 03 04
LE 04 03 02 01
|
其實只要記住,大小端說的都是低地址
大端就是低地址存儲高位
小端就是低地址存儲低位