关于Unicode的一次学习
本文最后更新于 195 天前,其中的信息可能已经有所发展或是发生改变。

引入:Unicode 家族

Unicode 家族主要是 UTF-8,UTF-16,UTF-32,它们都可以用多达 4 字节对字符进行编码

(Unicode Transfer Format)UTF 后面的数字表示单个编码所需的最小单位长度

因此 UTF-32 是定长编码

另外两个是不定长编码,UTF-8 可以是 1/2/3/4 字节,UTF-16 可以是 2/4 字节(本人愿称其为半定长编码)

这几种编码怎么来的?

Unicode 早期,推行的是定长编码(解析无需算法,性能极致)

定长编码包含 16 位和 32 位方案,即在支持生僻字符和节省内存间做一个取舍

  • 16 位能表示的 65536 个字符,称为基本多语言平面BMP,U+0000 到 U+FFFF)

由于以多字节为单位,UTF-16 和 32 引入了一个BOM机制,用于在文件开头标识内部字节是大端/小端

而后来的 UTF-8 以单字节为单位,所以也不需要BOM了

(至于编辑器中可选的 UTF-8 with BOM 编码,我的评价别管,别用。至于为什么,可以看最后)

可以看到很早支持 Unicode 的编程语言,都是支持 UTF-16

后来网络发展对数据传输的需要,使 UTF-32 这位在字符编码方面彻底入土了

当然,不考虑内存占用的话,UTF-32 依然具有信息量大、操作简单快捷的优点

主要是浪费的位太多,被互联网领域唾弃了

之后 UTF-16 接过编码全世界语言的大旗,成为了不定长编码,推出了代理对算法

再之后,算法更精妙、存储更紧凑的 UTF-8 诞生了,逐步成为如今 Unicode 的代表,是如今互联网主推的通用编码

下面是编码规则

UTF-8 的编码规则(以二进制形式表示)

1 字节编码

适用于 U+0000 至 U+007F 的码点,即原 ASCII 字符

二进制形式:0xxxxxxx

7 位有效数据,最高位为 0

2 字节编码

适用于 U+0080 至 U+07FF 的码点

二进制形式:110xxxxx 10xxxxxx

第一个字节以 110 开头,后续字节以 10 开头

11 位有效数据($5+6$)

3 字节编码

适用于 U+0800 至 U+FFFF 的码点

二进制形式:1110xxxx 10xxxxxx 10xxxxxx

第一个字节以 1110 开头,后续字节以 10 开头

16 位有效数据($4+6*2$),多了 5 位

4 字节编码

适用于 U+10000 至 U+10FFFF 的码点

二进制形式:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

第一个字节以 11110 开头,后续字节以 10 开头

21 位有效数据($3+6*3$),多了 5 位

具体编码示例(以最复杂的 4 字节为例)

U+1F600(😀,Unicode 码点 128512)

  1. 位数>16,超过 3 字节的表示能力,因此采用 4 字节
  2. 写成二进制:00000001 11110110 00000000
  3. 取 21 位:000|00001 11110110 00000000
  4. 填空题:11110___ 10______ 10______ 10______
  5. 填空后:11110000 10011111 10011000 10000000
  6. 结果为:0xF0 0x9F 0x98 0x80

顺便提供一个供验证的 Java 函数:

public void checkUtf8Code() {
    int codePoint = 0x1F600;
    String emoji = new String(Character.toChars(code));
    // 字符串转换为8位字节数组
    byte[] utf8Bytes = emoji.getBytes(StandardCharsets.UTF_8);
    // 以16进制输出
    System.out.print("UTF-8 编码的字节值: ");
    for (byte b : utf8Bytes) {
        System.out.printf("%02X ", b);
    }
}
  • 这是否应该是 Java 基础篇就讲的东西?

Java 基本类型 char 固定为16位,String 类内部也是以 char[] 存储值

创建 String 对象时会进行一次 encode 转为 UTF-16 存储,输出时会再进行一次转换,包括 charAt 获取某字符

charAt 是默认每个字符只占用 16 位,超出 BMP 的字符会以 2 个 char 保存,返回的结果就歪了

这就呼应了前面说 UTF-16 是半定长编码

相比之下,真正的不定长编码 UTF-8 在各大编程语言当中都是字节数组,然后外挂一系列处理算法

UTF-8 的实际编码范围?

Unicode 设计时存在一个上限

同时出于编码意义的唯一性,有些 >1 字节的编码是被跳过的

避免冗余编码

以 1 字节的 ASCII 码为例,当我在 7 位码前加 0000,便得到了值不变的 2 字节码

于是这些码被认为是冗余的,无效的

以此类推,把多字节多出的数据位设为 0 同理,都可以找到更少字节的表示

因此可以轻易得出,2 字节的前 4 位不能都为 0,3/4 字节的前 5 位不能都为 0

于是得出了一系列无效编码区间

避免与 UTF-16 混淆

UTF-16 通过代理对算法,表示超出 BMP 的字符

而代理对形式的范围是 U+D800 到 U+DFFF

于是 UTF-8 便跳过了这个范围

特别是在 3 字节编码的范围中,0xED 0xA00xED 0xBF 会被跳过,以确保不会编码无效的 U+D800 到 U+DFFF 范围的值。

避免超出有效的 Unicode 范围

Unicode 定义了码点的范围是 U+0000 到 U+10FFFF,超过这个范围的码点是无效的

因此 4 字节编码中,0xF4 后面的字节只能从 0x800x8F

前两字节示意:11110_100|10_001111

为什么是这个上限?

值得注意的是,这个上限正好是 65536 的 16 倍,它的制定与 UTF-16 的代理对息息相关

UTF-16 的代理对

代理对是 UTF-16 的一种不定长编码的实现机制,通过指定格式标识连续两个 16 位值表示的一个 UTF-16 字符

而这两个值被称为“代理对”(Surrogate Pairs),分为高代理和低代理

算法简述

仍以 U+1F600 (😀)为例。学编程真是令人心情愉悦啊(?)

  1. 位数>16,因此采用代理对

  2. 减去 0x10000,得到 0x1F600 - 0x10000 = 0xF600

  3. 在高位补 0,至 20 位,得到 0000|1111011000000000

  4. 分成高 10 位 00.0011.1101 和低 10 位 10.0000.0000,即 0x003D0x0200

  5. 高 10 位加上 0xD800,得到 0xD83D

    低 10 位加上 0xDC00,得到 0xDE00

  6. 于是得到编码后的值 0xD83D 0xDE00,即代理对

代理对占用了 U+D800 到 U+DFFF,不允许直接分配字符给它们,而是用于编码 U+10000 及以上的码点

以下是一个供验证的 Java 函数:

public static void checkUtf16Code() {
    int codePoint = 0x1F600;
    // 获取16位字符数组
    char[] surrogatePair = Character.toChars(codePoint);
    // 以16进制输出
    System.out.printf("高代理: %04X\n", (int) surrogatePair[0]);
    System.out.printf("低代理: %04X\n", (int) surrogatePair[1]);
}

补充:BOM 的标记

UTF-16 和 UTF-32 都选择 0xFE FF 为大端标记,0xFF FE 为小端标记(当然 UTF-32 前面再加 16 位 0)

于是文件开头的第一个字符,就是 BOM 标记

一般文件处理的时候,会不约而同地忽略这个字符

关于为什么使用 FEFF,其实就是业界常见的操作:拿用不到的最大字节作特殊用途

而排列顺序则遵从直觉:FE FF 顺序排列,对应高位在前的大端,而 FF FE 逆序排列,对应低位在前的小端

UTF-8 的 BOM

如果一路看到这里,应该能明白 UTF-8 的 EF BB BF 形式的 BOM 是怎么回事了

不卖关子,试着用 UTF-8 编码一下大端的 FE FF

UTF-8 按理说是不需要 BOM 的

至少 UTF-8 文件处理时,不见得会辨认第一个字符是文本字符,还是 BOM 标记

所以不是特殊需求的话,请不要 [这是什么,UTF-8 with BOM?保存一下]

封面图 pixiv ID:85499495
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇