字符串的传输与存储之UTF-8

Laeni
2023-05-11~2023-05-18

???

一个汉字占多大空间?

百度

image-20230416150416467

Google

image-20230416150604503

IDE Debug

image-20230416150918049

现在,我们得出一个结论:一个汉字占用 2 字节空间。

为什么保存在文本文件中的后会多占用 1 字节?

通过文本编辑器写入的文本

image-20230416151427694

通过编程方式写入的文本

image-20230416154753706

通过不同方式写入的文件结果是一样的,说明多出的 1 字节不是文本编辑器加上去特殊标识,而是汉字转换为字节流之后本身就占用 3 个字节。这可以通过查看实例代码中bytes变量加以证实。

猜测getBytes()时所使用的编码方式不同将得到不同长度的字节流

image-20230416160925505

通过以上实验验证了“不同的编码方式将得到的字节流”这一猜想,但同时也发现几个问题:

  1. 字符串在 Java 内存中是以何种编码存储的?为什么它和UTF-16有共同点,这是不是巧合?
  2. 既然GBK占用的空间更小,为什么我们平时不用它,而是选择占用空间更大的UTF-8

字符串在 Java 内存中如何存储?

A: 查看原码可知,Java String 类中只有两种编码(LATIN1UTF16),其他任何编码的字节流在转换为 Java 字符串时,都会将其转换为这两种编码的其中一种,具体规则是:如果所有字符全部都在LATIN1范围内,那就用LATIN1编码,否则用UTF16。这样做的原因是,UTF16包含了LATIN1,且LATIN1更省空间,所以优先使用LATIN1

即使在中国,为什么要选用UTF-8,而不用GBK

A: 最主要原因是因为GBK字符集虽然在存储中文时占用空间小,但是很多字符并不在它的范围内,而UTF-8这类字符集理论上能表示目前所有的字符。其他更细致原因后面分析,也可以参考UTF-8的由来

UTF

简介

UTF是“Unicode Transformation Format”的简称,中译为“Unicode 格式转换”。从它的名称看,它并非是字符集,而只是对Unicode字符集的字符按照一定的规则进行转换,以便能更好地进行网络传输或者存储,根据分组编码的位数不同,分为了UTF-8UTF-16UTF-32,即对于 UTF-8,应该说“UTF-8 编码”。但是从另外一方面,我们也可以将诸如UTF-8UTF-16这类编码方式看成是基于 Unicode 字符集的新字符集,所以平时说“UTF-8 字符集“也没问题。

???

  • UTF-16 和 UTF-32 是 UTF-8 的升级版吗?它们的区别是什么?
  • 我们今天用 UTF-8,后面哪天会不会用 UTF-16?
  • 未来会有 UTF-64 吗?

一步一步生成 UTF-8 编码

通过计算机,我们很容易知道字符“中”的 UTF-8 编码为[-28, -73, -83],但是它是怎样得到的?

UTF-8 编码规则

  1. UTF-8 是对 Unicode 码点二进制进行编码。
  2. 如果<Unicode码点二进制位数>小于等于7,该字符的 Unicode 码点即为 UTF-8 编码。
  3. 否则:
    1. n为某字符编码之后占用的字节数。
    2. 第一个字节:前n位都设为1,第n + 1位设为0
    3. 后面字节的前两位一律设为10,剩下的没有提及的二进制位,全部用这个字符的码点从右往左填充,其他如果还有空为则全部为0

根据上述规则,在不考虑 Unicode 容量的情况下,UTF-8 编码后的格式如下表:

字符大小UTF-8编码格式容量(位)
1字节0XXXXXXXX1 - 7
2字节110xxxxx 10xxxxxx8 - 11
3字节1110xxxx 10xxxxxx 10xxxxxx12 - 16
4字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxx17 - 21
5字节111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx22 - 26
6字节1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx27 - 31
7字节11111110 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx32 - 36

编码练习

A

  • 查询A的 Unicode 编号: \u41

  • \u41为 16 进制,对应 10 进制为: 65

  • 得到编号对应的二进制位: 1000001

  • A字符码点长度小于等于7,所以1000001就是A的 Unicode 码点,即 10 进制65

  • 查询的 Unicode 编号: \u4e2d

  • \u4e2d为 16 进制,对应 10 进制为: 20013

  • 得到编号对应的二进制位: 100111000101101

  • 字符码点长度为15,为多字节字符(由上表可知,为3字节字符)。

  • 1110xxxx 10xxxxxx 10xxxxxx

     100   111000   101101
1110xxxx 10xxxxxx 10xxxxxx
11100100 10111000 10101101
228      184      173
00011100 01001000 01010011
-28      -72      -83

UTF-16

1. 100111000101101
2. 0100111000101101
3. 01001110 00101101
4. 78       45
// UTF-32
00000000000000000100111000101101

工具

参考


发现错误或想为文章做出贡献? 在 GitHub 上编辑此页面!
© 2020-2025 All Right Reserved 滇ICP备17005647号-2