史上最简单易懂的 简析utf-8编码

tech2022-11-26  112

对于程序员来说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进制数字范围10xxxxxxx   0000到007F2110xxxxx10xxxxxx  0080 到07FF31110xxxx10xxxxxx10xxxxxx 0800到FFFF411110xxx10xxxxxx10xxxxxx10xxxxxx10000到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编码。

 

最新回复(0)