简析utf-8编码

对于程序员来说utf-8编码那真是很常见的了,但试问有多少人知道utf-8内在的原理?下面我尽量使用最短的时间让你了解utf-8。这里不讨论编码历史的发展,那对于现在的你可能毫无意义。假定你对二进制有所了解,并且理解无符号和有符号二进制。

简单的说utf-8就是一张数字和符号的映射表。那么解码的过程就是把二进制串解析成数字,然后从这张表中找到对应的字符。反之就是编码的过程。这个数字是无符号二进制。

那么问题随之就来了,怎么得到这个数字?要回答这个问题就得了解utf-8的结构了,utf-8编码可能占用1个字节也可能占用多个字节,但就目前规定最多占4个字节,所以说UTF-8编码为变长编码(科普:java中汉字的utf-8占用3个字节)。utf-8结构如下:

占用字节数 第1个字节 第2个字节 第3个字节 第4个字节 16进制数字范围
1 0xxxxxxx 0000到007F
2 110xxxxx 10xxxxxx 0080 到07FF
3 1110xxxx 10xxxxxx 10xxxxxx 0800到FFFF
4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 10000到1FFFFF

从这张结构图中我们就可以总结出找到数字的方法。

  • 如果一个字节的第一位为0,那么当前字符占用1个字节的空间。
  • 如果一个字节的第一位为110,那么当前字符占用2个字节的空间。
  • 如果一个字节的第一位为1110,那么当前字符占用3个字节的空间。
  • 如果一个字节的第一位为11110,那么当前字符占用4个字节的空间。

也就是说根据首字节就可以判断出该字符占用的二进制串的长度。自然就可以得出一个无符号的数字,然后用这个数字在utf-8的编码表中找到对应的字符就可以了。到现在你已经完全掌握了utf-8编码。

下面锁一个小例子,把下面的utf-8编码的二进制串解码:

111001101000100010010001111001111000100010110001111001001011110110100000

当然我们可以按照8位为单元把二进制串转成字节数组,然后按照utf-8编码传递给String对象就可以得到想要的答案,这里为了理解utf-8编码,模拟解码过程。代码如下:

package utf8;

public class TestUtf8 {

    public static void main(String[] args) throws Exception {
        String binaryString = "111001101000100010010001111001111000100010110001111001001011110110100000";
        while(!binaryString.equals("")){
            binaryString = decode(binaryString);
        }
    }

    /** * 解码 * @param binaryString * @return * @throws Exception */
    private static String decode(String binaryString)throws Exception{
        //找到当前二进制串
        String curBinaryString = findCurBinary(binaryString);
        //把当前二进制串装为字符,也就是找数字对应的字符
        binaryToChar(curBinaryString);
        //范围剩余二进制串
        return findLeftBinary(binaryString);
    }

    /** * 找到当前二进制串 * @param binaryString * @return */
    private static String findCurBinary(String binaryString){
        if(binaryString.startsWith("0")){//1个字节
            return binaryString.substring(0,8);
        }else if(binaryString.startsWith("110")){//2两个字节
            return binaryString.substring(0,16);
        }else if(binaryString.startsWith("1110")){//3个字节
            return binaryString.substring(0,24);
        }else if(binaryString.startsWith("11110")){//4个字节
            return binaryString.substring(0,32);
        }
        return "";
    }

    /** * 范围剩余二进制串 * @param binaryString * @return */
    private static String findLeftBinary(String binaryString){
        if(binaryString.startsWith("0")){//1个字节
            return binaryString.substring(8);
        }else if(binaryString.startsWith("110")){//2两个字节
            return binaryString.substring(16);
        }else if(binaryString.startsWith("1110")){//3个字节
            return binaryString.substring(24);
        }else if(binaryString.startsWith("11110")){//4个字节
            return binaryString.substring(32);
        }
        return "";
    }


    /** * 模拟查找数字对应字符 * @return */
    private static void binaryToChar(String binaryString)throws Exception{
        byte [] bytes = new byte[binaryString.length()/8];
        for(int i=0;i<bytes.length;i++){
            String substring = binaryString.substring(i * 8, i * 8 + 8);
            bytes[i] = Integer.valueOf(substring,2).byteValue();
        }

        String result = new String(bytes, "utf-8");
        System.out.print(result);
    }


}

当然你可以使用下面的方法:

package utf8;

public class TestUtf8 {

    public static void main(String[] args) throws Exception {
        String binaryString = "111001101000100010010001111001111000100010110001111001001011110110100000";
        binaryToChar(binaryString);
    }

    /** * 模拟查找数字对应字符 * @return */
    private static void binaryToChar(String binaryString)throws Exception{
        byte [] bytes = new byte[binaryString.length()/8];
        for(int i=0;i<bytes.length;i++){
            String substring = binaryString.substring(i * 8, i * 8 + 8);
            bytes[i] = Integer.valueOf(substring,2).byteValue();
        }

        String result = new String(bytes, "utf-8");
        System.out.print(result);
    }


}

想知道结果运行程序即可。

当然知道了utf-8编码的原理,你还可以解决一些实际问题,比如你的mysql数据库编码是utf-8的,那么mysql默认的utf-8编码最多支持3个字节。这就会导致存储一些占用4个字节的内容会导致错误。这个时候你可以利用你掌握的原理,把首字节开头为11110的二进制串过滤掉,然后再进行存储。也可以利用二进制串表示数字大于FFFF的就过滤掉。当然最简单的方法就是把数据库编码换成utf8mb4,也就是支持4个字节的utf-8编码。

相关文章
相关标签/搜索