# Unicode
<html><!-- 自定義樣式 -->
<style>
h1{color:#742222;}
h2{color:#8f3322;}
h3{color:#c05022;}
h4{color:#d75d21;}
h5{color:#dc6021;}
.key-class{
color:red;
}.shading{
background-color: #fee9cc;
padding: 2px 3px;
}.top-zzw-only, .bottom-zzw-only{
font-size:14px;color:white;
padding:2px 5px;position:fixed;
background:rgba(0,0,0,0.5);
}.bottom-zzw-only{
bottom:270px;right:50px;
}.top-zzw-only{
bottom:300px;right:50px;
}.top-zzw-only:hover, .bottom-zzw-only:hover{
background:rgba(220,96,33,0.8);
}
</style>
</html>
<!-- doc start -->
Unicode(統(tǒng)一碼、萬國碼、單一碼)是計算機科學領域里的一項業(yè)界標準,包括字符集束昵、編碼方案等含末。Unicode 是為了解決傳統(tǒng)的字符編碼方案的局限而產(chǎn)生的,它為每種語言中的每個字符設定了統(tǒng)一并且唯一的二進制編碼,以滿足跨語言、跨平臺進行文本轉(zhuǎn)換、處理的要求包斑。1990年開始研發(fā),1994年正式公布涕俗。
* [ASCII編碼](#ASCII)
* [非ASCII編碼](#NOTASCII)
* [unicode編碼](#unicode)
* [UTF-8](#UTF8)
* [UTF-16](#UTF16)
* [JAVA字符編碼](#javachar)
## <span id="ASCII">ASCII編碼</span>
ASCII 是用來表示英文字符的一種編碼規(guī)范罗丰。每個ASCII字符占用1 個字節(jié),因此再姑,ASCII編碼可以表示的最大字符數(shù)是255(00H—FFH)萌抵。這對于英文而言,是沒有問題的元镀,一般只什么用到前128個(00H--7FH,最高位為0)绍填。而最高位為1 的另128個字符(80H—FFH)被稱為“擴展ASCII”,一般用來存放英文的制表符栖疑、部分音標字符等等的一些其它符號讨永。
但是對于中文等比較復雜的語言,255個字符顯然不夠用遇革。于是卿闹,各個國家紛紛制定了自己的文字編碼規(guī)范,其中中文的文字編碼規(guī)范叫做“GB2312—80”澳淑, 它是和ASCII兼容的一種編碼規(guī)范比原, 其實就是利用擴展ASCII沒有真正標準化這一點,把一個中文字符用兩個擴展ASCII字符來表示杠巡,以區(qū)分ASCII碼部分。但是這個方法有問題雇寇,最大的問題就是中文的文字編碼和擴展ASCII 碼有重疊氢拥。而很多軟件利用擴展ASCII 碼的英文制表符來畫表格蚌铜,這樣的軟件用到中文系統(tǒng)中,這些表格就會被誤認作中文字符嫩海,出現(xiàn)亂碼冬殃。另外,由于各國和各地區(qū)都有自己的文字編碼規(guī)則叁怪,它們互相沖突审葬,這給各國和各地區(qū)交換信息帶來了很大的麻煩。
## <span id="NOTASCII">非ASCII編碼</span>
英語用128個符號編碼就夠了奕谭,但是用來表示其他語言涣觉,128個符號是不夠的。比如血柳,在法語中官册,字母上方有注音符號,它就無法用ASCII碼表示难捌。于是膝宁,一些歐洲國家就決定,利用字節(jié)中閑置的最高位編入新的符號根吁。比如员淫,法語中的é的編碼為130(二進制10000010)。這樣一來击敌,這些歐洲國家使用的編碼體系满粗,可以表示最多256個符號。
但是愚争,這里又出現(xiàn)了新的問題映皆。不同的國家有不同的字母,因此轰枝,哪怕它們都使用256個符號的編碼方式捅彻,代表的字母卻不一樣。比如鞍陨,130在法語編碼中代表了é步淹,在希伯來語編碼中卻代表了字母Gimel (?),在俄語編碼中又會代表另一個符號诚撵。但是不管怎樣缭裆,所有這些編碼方式中,0--127表示的符號是一樣的寿烟,不一樣的只是128--255的這一段澈驼。
至于亞洲國家的文字,使用的符號就更多了筛武,漢字就多達10萬左右缝其。一個字節(jié)只能表示256種符號挎塌,肯定是不夠的,就必須使用多個字節(jié)表達一個符號内边。比如榴都,簡體中文常見的編碼方式是GB2312,使用兩個字節(jié)表示一個漢字漠其,所以理論上最多可以表示256x256=65536個符號嘴高。
## <span id="unicode">unicode編碼</span>
各個國家都像中國這樣搞出一套自己的編碼標準,結(jié)果互相之間誰也不懂誰的編碼和屎,誰也不支持別人的編碼拴驮。當時的中國人想讓電腦顯示漢字,就必須裝上一個”漢字系統(tǒng)”眶俩,專門用來處理漢字的顯示莹汤、輸入的問題,裝錯了字符系統(tǒng)颠印,顯示就會亂了套纲岭。這怎么辦?就在這時线罕,一個叫 ISO (國際標誰化組織)的國際組織決定著手解決這個問題止潮。他們采用的方法很簡單:廢了所有的地區(qū)性編碼方案,重新搞一個包括了地球上所有文化钞楼、所有字母和符號的編碼喇闸!他們打算叫它”Universal Multiple-Octet Coded Character Set”,簡稱 UCS, 俗稱 “UNICODE”询件。
* 中國人民通過對 ASCII 編碼的中文擴充改造燃乍,產(chǎn)生了 GB2312 編碼,可以表示6000多個常用漢字宛琅。
* 漢字實在是太多了刻蟹,包括繁體和各種字符,于是產(chǎn)生了 GBK 編碼嘿辟,它包括了 GB2312 中的編碼舆瘪,同時擴充了很多。
* 中國是個多民族國家红伦,各個民族幾乎都有自己獨立的語言系統(tǒng)英古,為了表示那些字符,繼續(xù)把 GBK 編碼擴充為 GB18030 編碼昙读。
* 每個國家都像中國一樣召调,把自己的語言編碼,于是出現(xiàn)了各種各樣的編碼,如果你不安裝相應的編碼某残,就無法解釋相應編碼想表達的內(nèi)容国撵。
* 終于陵吸,有個叫 ISO 的組織看不下去了玻墅。他們一起創(chuàng)造了一種編碼 UNICODE ,這種編碼非常大壮虫,大到可以容納世界上任何一個文字和標志澳厢。所以只要電腦上有 UNICODE 這種編碼系統(tǒng),無論是全球哪種文字囚似,只需要保存文件的時候剩拢,保存成 UNICODE 編碼就可以被其他電腦正常解釋。
* UNICODE 在網(wǎng)絡傳輸中饶唤,出現(xiàn)了兩個標準 UTF-8 和 UTF-16徐伐,分別每次傳輸 8個位和 16個位。
于是就會有人產(chǎn)生疑問募狂,UTF-8 既然能保存那么多文字办素、符號,為什么國內(nèi)還有這么多使用 GBK 等編碼的人祸穷?因為 UTF-8 等編碼體積比較大性穿,占電腦空間比較多,如果面向的使用人群絕大部分都是中國人雷滚,用 GBK 等編碼也可以需曾。但是目前的電腦來看,硬盤都是白菜價祈远,電腦性能也已經(jīng)足夠無視這點性能的消耗了呆万。所以推薦所有的網(wǎng)頁使用統(tǒng)一編碼:UTF-8。
常用的unicode編碼值范圍為“0-0xFFFF”
<span class="shading">實際unicode編碼值范圍為“0-0x10FFFF”</span>
應當注意车份,Unicode只是一個符號集谋减,它只規(guī)定了符號的二進制代碼,卻沒有規(guī)定這個二進制代碼應該如何存儲躬充。
有些字符用一個字節(jié)就能表示逃顶,有些則需要2個或3個字節(jié),甚至有4個字節(jié)充甚。如果unicode統(tǒng)一規(guī)定以政,每個符號用三個或四個字節(jié)表示,那么每個英文字母勢必有二到三個字節(jié)是0伴找,這對于存儲來說是極大的浪費盈蛮。
所以unicode很長一段時間內(nèi)無法推廣。
互聯(lián)網(wǎng)的普及技矮,強烈要求出現(xiàn)統(tǒng)一的編碼方式抖誉。UTF-8就是在互聯(lián)網(wǎng)上使用最廣的一種Unicode的實現(xiàn)方式殊轴。其他實現(xiàn)方式還包括UTF-16(字符用兩個字節(jié)或四個字節(jié)表示)和UTF-32(字符用四個字節(jié)表示),不過在互聯(lián)網(wǎng)上基本不用袒炉。
## <span id="UTF8">UTF-8</span>
**UTF-8是Unicode的實現(xiàn)方式之一旁理。**
UTF-8最大的一個特點,就是它是一種變長的編碼方式我磁。它可以使用1~4個字節(jié)表示一個符號孽文,根據(jù)不同的符號而變化字節(jié)長度。
UTF-8的編碼規(guī)則很簡單夺艰,只有二條:
* 對于單字節(jié)的符號芋哭,字節(jié)的第一位設為0,后面7位為這個符號的unicode碼郁副。因此對于英語字母减牺,UTF-8編碼和ASCII碼是相同的。
* 對于n字節(jié)的符號(n>1)存谎,第一個字節(jié)的前n位都設為1拔疚,第n+1位設為0,后面字節(jié)的前兩位一律設為10愕贡。剩下的沒有提及的二進制位草雕,全部為這個符號的unicode碼。
| unicode符號范圍(十六進制) | UTF-8編碼方式(二進制) |
| --------------- | ------------- |
| 00000000-0000007F | 0xxxxxxx |
| 00000080-000007FF | 110xxxxx 10xxxxxx |
| 00000800-0000FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
| 00010000-0010FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
以漢字“鄭”為例:
它的unicode編碼為90d1(1001000011010001)固以。根據(jù)上表墩虹,可以發(fā)現(xiàn)90d1處在第三行的范圍,即格式是“1110xxxx 10xxxxxx 10xxxxxx”憨琳。然后诫钓,從“鄭”的最后一個二進制位開始,依次從后向前填入格式中的x篙螟,多出的位補0菌湃。這樣就得到了,“鄭”的UTF-8編碼為:
11101001 10000011 10010001
我們可以用下面一段代碼證實:
```java
public static void main(String[] args) throws UnsupportedEncodingException {
String zheng = "鄭";
byte [] bs = new byte[3];
bs = zheng.getBytes("UTF-8");
for (int i = 0; i < bs.length; i++) {
System.out.print(convert(bs[i])+" ");
}
}
/**
* 返回整數(shù)byte的二進制補碼
* @param number
* @return
*/
public static String convert(byte number){
int length = 8;//1個字節(jié)遍略,8位長度
StringBuilder builder = new StringBuilder("");
for (int i = length-1; i >= 0; i--) {
//每一位和1比較惧所,如果為0表示對應的位數(shù)為0,如果為1表示對應的位數(shù)為1
if(((1<<i)&number) == 0){
builder = builder.append("0");
}else {
builder = builder.append("1");
}
}
return builder.toString();
}
```
運行代碼绪杏,輸出:11101001 10000011 10010001
## <span id="UTF16">UTF-16</span>
UTF-16是Unicode字符集的另一種轉(zhuǎn)換方式下愈,即把Unicode的碼位轉(zhuǎn)換為16比特長的碼元串行,以用于數(shù)據(jù)存儲或傳遞蕾久。
UTF-16的基本單位是2個字節(jié)势似,即16位。
2個字節(jié)能表達的最大十六進制數(shù)位0xFFFF。那么對于大于這個數(shù)的unicode編碼履因,如何用utf-16表示障簿?
###### 1.代理區(qū)
因為unicode字符集的編碼值范圍為0-0x10FFFF,因而大于等于0x10000的輔助平面區(qū)的編碼值無法用2個字節(jié)表示栅迄。
所以unicode標準規(guī)定:
**基于多平面語言內(nèi)站故,U+D800 - U+DFFF的值不對應于任何字符,為代理區(qū)霞篡。**
因此世蔗,UTF-16利用保留下來的代理區(qū)的碼位來對輔助平面的字符的碼位進行編碼端逼。
###### 2.第一平面
第一個unicode平面(BMP)朗兵,碼位從U+0000至U+FFFF(除去代理區(qū)),包含了常用字符顶滩。UTF-16與UCS-2編碼在這個范圍內(nèi)為單個16比特長的碼元余掖,數(shù)值等價于對應的碼位。
即:在第一平面礁鲁,unicode編碼值是多少盐欺,相應的UTF-16的編碼值也為多少。
###### 3.輔助平面
輔助平面中的碼元仅醇,大于等于0x10000冗美,在UTF-16中被編碼為一對16比特長的碼元。很顯然析二,每一個碼元對應的碼位值都應該在unicode第一平面的代理區(qū)內(nèi)粉洼,從而保證UTF-16的正確映射。具體方法是:
* 碼位減去0x10000叶摄,得到的值的范圍為0-0xFFFFF(unicode最大碼位為0x10FFFF属韧,第一平面能表示的最大碼位為0xFFFF,所以第二平面最小碼位為0x10000)蛤吓。寫成二進制為yyyy yyyy yyxx xxxx xxxx
* 將高位的10比特值(yyyyyyyyyy)加上0xD800(1101100000000000)得到第一個碼元宵喂,稱作高位代理。值的范圍為0xD800-0xDBFF会傲。高位代理也稱作前導代理锅棕。
* 將低位的10比特值(xxxxxxxxxx)加上0xDC00(1101110000000000),得到第二個碼元淌山,稱作低位代理裸燎。值的范圍為0xDC00-0xDFFF。低位代理也稱作后尾代理艾岂。
* 最終的UTF-16(4字節(jié))的編碼就是:110110yyyyyyyyyy 110111xxxxxxxxxx
###### 4.總結(jié)
可以看出顺少,高位代理、低位代理、BMP中的有效字符的碼位脆炎,三者是互不重疊的梅猿。
對于任意一個碼元(2字節(jié),輔助平面的值含有2個碼元)秒裕,僅可通過碼元本省便可確定該碼元所代表的含義袱蚓。
這意味著UTF-16是自同步的:可以通過僅檢查一個碼元就可以判定給定字符的下一個字符的起始碼元。
UTF-32固定采用4字節(jié)編碼几蜻,一一映射unicode編碼喇潘,這里不做討論。
###### 5.UTF-8&UTF-16
對于unicode第一平面的字符來說梭稚,UTF-8用1至3個字節(jié)來存儲颖低;UTF-16永遠是用2個字節(jié)來存儲,二進制值就是對應的unicode編碼值弧烤。
同樣以漢字“鄭”為例:
它的unicode編碼為0x90d1(1001000011010001)忱屑。所以它的UTF-16的編碼同樣為1001000011010001。
我們可以用上面的代碼修改編碼后證實:
```java
public static void main(String[] args) throws UnsupportedEncodingException {
String zheng = "鄭";
byte [] bs = new byte[3];
bs = zheng.getBytes("UTF-16");
for (int i = 0; i < bs.length; i++) {
System.out.print(convert(bs[i])+" ");
}
}
/**
* 返回整數(shù)byte的二進制補碼
* @param number
* @return
*/
public static String convert(byte number){
int length = 8;//1個字節(jié)暇昂,8位長度
StringBuilder builder = new StringBuilder("");
for (int i = length-1; i >= 0; i--) {
//每一位和1比較莺戒,如果為0表示對應的位數(shù)為0,如果為1表示對應的位數(shù)為1
if(((1<<i)&number) == 0){
builder = builder.append("0");
}else {
builder = builder.append("1");
}
}
return builder.toString();
}
```
運行代碼急波,輸出:11111110 11111111 10010000 11010001?
(11111110 11111111是UTF-16的文件標識从铲,UTF-16文件的開頭都會加上這個)
由于現(xiàn)實生活中常用的字符都是在unicode的第一平面內(nèi),由此可以看出澄暮。對于含有英文較多的文件名段,用UTF-8比較節(jié)省空間,對于含有中文較多的文件赏寇,用UTF-16比較節(jié)省空間吉嫩。
對于unicode輔助平面的字符,其編碼值大于等于0x10000嗅定,此時UTF-8同UTF-16一樣都是用4個字節(jié)去存儲自娩。
這里以“??”為例:
它的unicode編碼為0x22106(100010000000010110)
依然是上面的代碼(僅以UTF-16為例):
```java
public static void main(String[] args) throws UnsupportedEncodingException {
String zheng = "??";
byte [] bs = new byte[3];
bs = zheng.getBytes("UTF-16");
for (int i = 0; i < bs.length; i++) {
System.out.print(convert(bs[i])+" ");
}
}
/**
* 返回整數(shù)byte的二進制補碼
* @param number
* @return
*/
public static String convert(byte number){
int length = 8;//1個字節(jié),8位長度
StringBuilder builder = new StringBuilder("");
for (int i = length-1; i >= 0; i--) {
//每一位和1比較渠退,如果為0表示對應的位數(shù)為0忙迁,如果為1表示對應的位數(shù)為1
if(((1<<i)&number) == 0){
builder = builder.append("0");
}else {
builder = builder.append("1");
}
}
return builder.toString();
}
```
運行代碼,輸出:11111110 11111111 11011000 01001000 11011100 00010110
去掉前面的UTF-16標識碎乃,得到:
11011000 01001000 11011100 00010110
第一個碼元減去0xD800得到:00 01001000?
第二個碼元減去0xDC00得到:00 00010110
兩者合并再加上0x10000得到“??”的unicode編碼為:
10 00100000 00010110
即0x22016
## <span id="javachar">JAVA字符編碼</span>
java的每個字符都是對應一個unicode編碼姊扔。準確的來講,是第一平面的unicode編碼梅誓。以下面的代碼為例:
```java
char c1 = '鄭';//
char c2 = '??';//編譯報錯
```
java中恰梢,一個字符的存儲長度為2個字節(jié)佛南。由于“??”超過了2個字節(jié)能存儲的范圍,所以第二行即c2在編譯期就報錯嵌言。
再看一段代碼:
```java
System.out.println("鄭".length());
System.out.println("??".length());
```
運行代碼嗅回,輸出:1、2
顯然摧茴,字符串"鄭"在java中占用了2個字節(jié)绵载,"??"占用了4個字節(jié)。
注意:這里說的java字符編碼不是指java文件的存儲編碼(文件存儲編碼是開發(fā)人員指定的苛白,通常用UTF-8)娃豹,而是指程序編譯和執(zhí)行的編碼格式。
在java中购裙,char類型的變量可以直接轉(zhuǎn)換為整型變量懂版,可以利用這一點了解到字符在java中的編碼格式。
以漢字"鄭"為例缓窜,將上面的代碼加以修改:
```java
public static void main(String[] args) throws UnsupportedEncodingException {
char c = '鄭';
short s = (short) c;
System.out.print(convert(s));
}
/**
* 返回整數(shù)short的二進制補碼
* @param number
* @return
*/
public static String convert(short number){
int length = 16;//2個字節(jié)定续,16位長度
StringBuilder builder = new StringBuilder("");
for (int i = length-1; i >= 0; i--) {
//每一位和1比較,如果為0表示對應的位數(shù)為0禾锤,如果為1表示對應的位數(shù)為1
if(((1<<i)&number) == 0){
builder = builder.append("0");
}else {
builder = builder.append("1");
}
}
return builder.toString();
}
```
運行代碼,輸出:1001000011010001
輸出的二進制碼剛好為“鄭”對應的unicode編碼摹察。
再以輔助平面內(nèi)的字符“??”為例:
由于char類型無法存儲輔助平面內(nèi)的字符恩掷,所以這里我們重新修改一下代碼,利用字符串獲取它在java中的存儲編碼供嚎。
在這個例子中黄娘,將字符“鄭”也加進去克滴,以比較在java中第一平面和輔助平面的存儲方式之不同。
```java
public static void main(String[] args) {
System.out.println(convert("鄭"));
System.out.println(convert("??"));
}
/**
* 字符串轉(zhuǎn)Unicode碼
* @param str
* @return
*/
public static String convert(String str){
str = (str == null ? "" : str);
String tmp;
StringBuffer sb = new StringBuffer(1000);
char c;
int i, j;
sb.setLength(0);
for (i = 0; i < str.length(); i++){
c = str.charAt(i);
sb.append("\\u");
j = (c >>>8); //取出高8位
tmp = Integer.toHexString(j);
if (tmp.length() == 1)
sb.append("0");
sb.append(tmp);
j = (c & 0xFF); //取出低8位
tmp = Integer.toHexString(j);
if (tmp.length() == 1)
sb.append("0");
sb.append(tmp);
}
return (new String(sb));
}
```
運行代碼誓焦,輸出:
```
\u90d1
\ud848\udc16
```
將其轉(zhuǎn)換成二進制分別為:
```
1001000011010001
1101100001001000 1101110000010110
```
與UTF-16一致!W琶薄T游啊仍翰!
## 參考文檔
<a target="_blank">Unicdoe【真正的完整碼表】對照表</a>
<br>
<a target="_blank">Unicode編碼表</a>
<br>
<a target="_blank">JAVA 字符串編碼總結(jié)</a>
<br>
<a target="_blank">ASCII對照表</a>
<br>
<a target="_blank">字符編碼中ASCII、Unicode和UTF-8的區(qū)別</a>
<br>
<a target="_blank">網(wǎng)頁編碼就是那點事</a>
<br>
<a target="_blank">unicode予借、utf-8越平、ansi的故事及其相互轉(zhuǎn)換</a>
<br>
<a target="_blank">utf16編碼格式</a>
<br>
-----
<br>
thanks频蛔!
<a href="javascript:scrollTo(0,0);" class='top-zzw-only'>頂部</a>
<a href="javascript:scrollTo(0,document.body.scrollHeight);" class='bottom-zzw-only'>底部</a>
**<div style="text-align: right;color: red;font-size: 20px;">--鄭澤旺</div>**
<div style="text-align: right;" id='bottom-zzw-only'>2017-10-12</div>