浅谈Java中字符串的UTF-8编码格式

想探讨Java中字符串的UTF-8编码格式,首先想到的是直接将字符串的字节码打印输出。例如,以下代码试图打印中文”赵”字的UTF-8编码的字节数组。


public class Utf8 {
    public static void main(String[] args) {
        byte[] bts = "赵".getBytes(StandardCharsets.UTF_8);
        System.out.println(Arrays.toString(bts));
    }
}

输出结果如下:

[-24, -75, -75]

输出结果是3个负数,如何理解这个输出结果?

首先,我们知道在Java中,字符串都是使用Unicode(通用字符集)进行编码的。“赵”字在Unicode中的code point(位点)为\u8d75。而本例中UTF-8(8-bit Unicode Transformation Format)是Unicode最常用的一种编码格式。

UTF-8的编码格式规则如下:

代码范围(16进制)二进制编码格式备注
000000 – 00007f0xxxxxxxASCII字符范围,以0开始
000080 – 0007ff110xxxxx 10xxxxxx第1个字节110开始, 第2个字节10开始
000800 – 00D7ff
00e000 – 00ffff

1110xxxx 10xxxxxx 10xxxxxx
第1个字节1110开始,其他字节10开始
010000 – 10ffff11110xxx 10xxxxxx 10xxxxxx 10xxxxxx第1个字节11110开始,其他字节10开始
UTF-8编码格式

编码规则可以看出,UTF-8是一种可变长度字符编码,也是一种前缀码。“赵”的位点16进制8d75,属于3字节的编码规则,所以UTF-8编码格式应该为

1110xxxx 10xxxxxx 10xxxxxx

8d75的二进制表示为

1000 1101 0111 0101

则“赵”的UTF-8编码二进制表示为

11101000 10110101 10110101

Java中对字节进行打印,其实就是将二进制表示的十进制真值输出。而Java中实际存储的是二进制的补码,正数的补码即真值,而负数的补码减1然后取反码即为真值的二进制表示。

因为3个字节第1位符号位(0表示正数,1表示负数)都为1,所以都为负数。则UTF-8编码字节数组的真值应该表示为

10011000 11001011 11001011

最后进行二进制转十进制转换,后7位为数值的绝对值。所以,打印结果为

[-24, -75, -75]

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注