前兩天在網上看到一篇關於編碼的討論,仔細學習了一下unicode,utf8,utf16的定義。這篇博客旨在讓讀者真正理解他們是什麼。html
在閱讀本文以前建議讀者先去閱讀這篇文章:http://www.freebuf.com/articles/others-articles/25623.html,若是你沒有耐心讀完他也不要緊,只須要明白三個道理:java
1,這個世界上歷來沒有純文本這回事,若是你想讀出一個字符串,你必須知道它的編碼。若是你不知道一段數據流的編碼方式,你就永遠不會知道這裏面的內容。網絡
2,Unicode是一個簡單的標準,用來把字符映射到數字上。Unicode協會的人會幫你處理全部幕後的問題,包括爲新字符指定編碼。咱們用的全部字符都在unicode裏面有對應的映射,每一個映射稱爲一個碼點(http://en.wikipedia.org/wiki/Code_point)學習
3,Unicode並不告訴你字符是怎麼編碼成字節的。這是被編碼方案決定的,經過UTF來指定。編碼
讀完前面這篇文章以後你也許就瞭解了一個二進制流到屏幕字符的過程:spa
二進制流->根據編碼方式解碼出碼點->根據unicode碼點解釋出字符->系統渲染繪出這個字符設計
文本字符保存到計算機上的過程:code
輸入字符->根據字符找到對應碼點->根據編碼方式把碼點編碼成二進制流->保存二進制流到硬盤上orm
從這個過程咱們能夠知道能不能從二進制流讀取出字符關鍵就在於能不能找到二進制流的編碼,掌握了編碼方式的信息就能夠用對應的逆過程解碼。htm
看到這裏有讀者必定會問:爲何要編碼,根據二進制流計算碼點很差嗎?
緣由是良好設計的編碼能夠爲咱們提供不少附加的功能,包括容錯糾錯(在網絡通訊中尤爲重要),自同步(沒必要從文本頭部開始就能夠解碼)等等。編碼從信息論的角度上來講就是增長了冗餘的信息,冗餘的這部分信息就能夠爲咱們提供額外的功能。
咱們來看utf8和utf16具體是如何編碼的:
Utf8有以下特色:
1.可變長編碼,由第一個字節決定該字符編碼長度
2.向下兼容ascii碼(這也是爲何用utf8編碼能夠完美打開ascii文本文件)
Utf8的編碼規則:
開頭字節以若干個1開頭(長度爲幾就有幾個1,所以只要讀完開頭字節就能夠知道本字符共有多少個字節),後接1個0.後續字節都以10開頭
具體來舉幾個例子:
字符 | 碼點 | 二進制 UTF-8 | 16進制 UTF-8 | |
---|---|---|---|---|
$ | U+0024 |
0100100 |
00100100 |
24 |
¢ | U+00A2 |
000 10100010 |
11000010 10100010 |
C2 A2 |
€ | U+20AC |
00100000 10101100 |
11100010 10000010 10101100 |
E2 82 AC |
𤭢 | U+24B62 |
00010 01001011 01100010 |
11110000 10100100 10101101 10100010 |
F0 A4 AD A2 |
public class Utf8 { /** * @param codePoint in unicode * @return corresponding utf8 bytes * @throws Exception */ private static final long RightSix = (1 << 6) - 1; private static final long PrefixForContinuasByte = 1 << 7; public static long EncodeToUtf8(long codePoint) throws Exception { if (codePoint < 0 || codePoint > 0x1FFFFF) throw new Exception("Illegal code point!"); if (codePoint <= 0x007F) { return codePoint;// ascii character } else if (codePoint <= 0x07FF) { long byte1 = (6 << 5) + (codePoint >> 6); long byte2 = PrefixForContinuasByte + (codePoint & RightSix); return (byte1 << 8) + byte2; } else if (codePoint <= 0xFFFF) { long byte1 = (14 << 4) + (codePoint >> 12); long byte2 = PrefixForContinuasByte + ((codePoint >> 6) & RightSix); long byte3 = PrefixForContinuasByte + (codePoint & RightSix); return (byte1 << 16) + (byte2 << 8) + byte3; } else { long byte1 = (30 << 3) + (codePoint >> 18); long byte2 = PrefixForContinuasByte + ((codePoint >> 12) & RightSix); long byte3 = PrefixForContinuasByte + ((codePoint >> 6) & RightSix); long byte4 = PrefixForContinuasByte + (codePoint & RightSix); return (byte1 << 24) + (byte2 << 16) + (byte3 << 8) + byte4; } } public static void main(String[] args) { try { while (true) { System.out.print("Input a number in Hex format:"); Scanner sc = new Scanner(System.in); String s = sc.nextLine(); // System.out.println("it is "+HexStringToLong(s)+" in decimal format"); long utf8 = EncodeToUtf8(HexStringToLong(s)); String hexString = Long.toHexString(utf8); System.out.println("You input " + s + " in Hex format and we encode it to utf8 character " + hexString); } } catch (Exception e) { System.out.println(e.getLocalizedMessage()); // TODO: handle exception } } }
運行結果:
Input a number in Hex format:24 You input 24 in Hex format and we encode it to utf8 character 24 Input a number in Hex format:A2 You input A2 in Hex format and we encode it to utf8 character c2a2 Input a number in Hex format:20AC You input 20AC in Hex format and we encode it to utf8 character e282ac Input a number in Hex format:24B62 You input 24B62 in Hex format and we encode it to utf8 character f0a4ada2
關於UTF-16的編碼規則,讀者能夠參考這篇文章:http://en.wikipedia.org/wiki/UTF-16
這裏附上UTF16-BE的編碼代碼:
public class Utf16 { /** * @param codePoint in unicode * @return corresponding utf16 bytes * @throws Numberformat Exception */ private static final long Substracted=0x10000; private static final long AddToHigh=0xD800; private static final long AddToLow=0xDC00; private static long HexStringToLong(String s) { if (s.length() == 0) return 0; long ans = 0; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c >= '0' && c <= '9') ans = (ans << 4) + (c - '0'); else if (c >= 'A' && c <= 'F') ans = (ans << 4) + (c - 'A' + 10); else throw new NumberFormatException(); } return ans; } public static long EncodeToUtf16BE(long codePoint) throws Exception { if(codePoint<0||(codePoint<=0xDFFF&&codePoint>=0xD800)||codePoint>0x10FFFF) throw new NumberFormatException(); if(codePoint<=0xD7FF)//Basic Multilingual Plane { return codePoint; } else { long sub=codePoint-Substracted; long high=sub>>10; long low=sub&0x3FF; long word1=AddToHigh+high; long word2=AddToLow+low; return (word1<<16)+word2; } } public static void main(String[] args) { while(true) { System.out.print("Input a number in hex format"); Scanner sc=new Scanner(System.in); String s=sc.nextLine(); try { String utf16=Long.toHexString(EncodeToUtf16BE(HexStringToLong(s))); System.out.println("You input "+s+" we encode it to utf16-BE "+utf16); } catch (Exception e) { e.printStackTrace(); } } } }