你真的理解編碼嗎?unicode,utf8,utf16詳解

背景

前兩天在網上看到一篇關於編碼的討論,仔細學習了一下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的編碼規則

咱們來看utf8和utf16具體是如何編碼的:

Utf8有以下特色:

1.可變長編碼,由第一個字節決定該字符編碼長度

2.向下兼容ascii碼(這也是爲何用utf8編碼能夠完美打開ascii文本文件)

 

Utf8的編碼規則:

  1. 一個字節的編碼徹底用於ascii碼(從0-127)
  2. 大於127的碼點都用多字節來編碼,多字節包含開頭字節和後續字節

開頭字節以若干個1開頭(長度爲幾就有幾個1,所以只要讀完開頭字節就能夠知道本字符共有多少個字節),後接1個0.後續字節都以10開頭

  1. 從右到作,後續字節每一個字節佔用原碼點6個位,剩餘的放在開頭字節。
  2. 開頭字節和後續字節不共享任何數據,所以utf8是自同步的。舉例來講咱們看到一個字節以110…開頭時,咱們就知道這是一個2字節的字符的開頭字節。

具體來舉幾個例子:

 

字符 碼點 二進制 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

 

  

實現了UTF8編碼的java代碼:

  

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();
            }
            
            
        }
    }
}
相關文章
相關標籤/搜索