Skip to main content

utf8

1. Unicode 转 UTF-8

1.1 Unicode 是什么?

Unicode 是一个字符集标准,为世界上几乎所有的字符分配了一个唯一的编号,称为码点(Code Point)

  • 表示方式:U+xxxx(十六进制)
  • 例如:
    • 'A' = U+0041 = 65(十进制)
    • '中' = U+4E2D = 20013(十进制)
    • '😀' = U+1F600 = 128512(十进制)

1.2 UTF-8 是什么?

UTF-8 是 Unicode 的一种编码方式 ,将 Unicode 码点转换为字节序列,用于存储和传输。

1.3 为什么需要 UTF-8?

  • Unicode 只定义了字符的编号,但没有规定如何在计算机中存储
  • UTF-8 是最流行的 Unicode 编码方式
  • 特点:
    • 变长编码(1-4字节)
    • 兼容 ASCII(ASCII字符只用1字节)
    • 节省空间

2. UTF-8 编码规则

2.1 编码规则表

Unicode 范围十进制范围字节数UTF-8 编码格式数据位数
U+0000 ~ U+007F0 ~ 1271字节0xxxxxxx7位
U+0080 ~ U+07FF128 ~ 20472字节110xxxxx 10xxxxxx11位
U+0800 ~ U+FFFF2048 ~ 655353字节1110xxxx 10xxxxxx 10xxxxxx16位
U+10000 ~ U+10FFFF65536 ~ 11141114字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxx21位

通常,在计算机领域里,FF十六进制 表示法,写作 0xFF
十六进制中:

  • F 代表十进制的 15
  • 每一位的权值是 16 的幂。

在十六进制中:F 代表十进制的 15 7F 可以看作是 7 * 16 + 15,所以 7 * 16 + 15 = 112 + 15 = 127 07FF 可以看作是 0x07FF = 7 × 16² + 15 × 16 + 15 = 1792 + 240 + 15 = 2047 FFFF 可以看作是 0xFFFF = 15 × 16³ + 15 × 16² + 15 × 16 + 15 = 61440 + 3840 + 240 + 15 = 65535 10FFFF 可以看作是 0x10FFFF = 1 × 16⁵ + 0 × 16⁴ + 15 × 16³ + 15 × 16² + 15 × 16 + 15 = 1048576 + 0 + 61440 + 3840 + 240 + 15 = 1114111

2.2 规则说明

  1. 首字节标识 :通过首字节的前几位判断字节数

    • 0xxxxxxx:1字节(ASCII)
    • 110xxxxx:2字节的首字节
    • 1110xxxx:3字节的首字节
    • 11110xxx:4字节的首字节
  2. 后续字节标识 :所有后续字节都以 10 开头

    • 这样可以区分首字节和后续字节
  3. 数据位填充x 表示实际数据位

    • 将 Unicode 码点的二进制从低位到高位依次填入

2.3 算法实现

2.3.1 方法1:Python 内置方法(最简单)

# 1.获取 Unicode 码点
码点 = ord('中')
print(f'U+{码点:04X}') # U+4E2D
print(码点) # 20013

# 2.从码点创建字符
字符 = chr(0x4E2D)
print(字符) # '中'

# 3.Unicode 字符 → UTF-8 字节
utf8_bytes = '中'.encode('utf-8')
print(utf8_bytes) # b'\xe4\xb8\xad'
print(utf8_bytes.hex()) # 'e4b8ad'

# 4.UTF-8 字节 → Unicode 字符
原字符 = utf8_bytes.decode('utf-8')
print(原字符) # '中'

2.3.2 方法2:实现原理

# 定义函数,将Unicode码点转为UTF-8字节序列
def unicode_to_utf8(codepoint):
"""
将 Unicode 码点转换为 UTF-8 字节序列

参数:
codepoint: Unicode 码点(整数)

返回:
bytes: UTF-8 编码的字节序列
"""
# 检查码点是否在有效范围内
if codepoint < 0 or codepoint > 0x10FFFF:
raise ValueError(f"无效的 Unicode 码点: {codepoint}")

# 如果码点小于等于0x7F,用1字节表示(ASCII)
if codepoint <= 0x7F:
# 构造单字节
return bytes([codepoint])

# 如果码点小于等于0x7FF,用2字节表示
elif codepoint <= 0x7FF:
# 构造第1字节(110xxxxx)
byte1 = 0b11000000 | (codepoint >> 6)
# 构造第2字节(10xxxxxx)
byte2 = 0b10000000 | (codepoint & 0b00111111)
# 返回组合后的2字节
return bytes([byte1, byte2])

# 如果码点小于等于0xFFFF,用3字节表示
elif codepoint <= 0xFFFF:
# 构造第1字节(1110xxxx)
byte1 = 0b11100000 | (codepoint >> 12)
# 构造第2字节(10xxxxxx)
byte2 = 0b10000000 | ((codepoint >> 6) & 0b00111111)
# 构造第3字节(10xxxxxx)
byte3 = 0b10000000 | (codepoint & 0b00111111)
# 返回组合后的3字节
return bytes([byte1, byte2, byte3])

# 其它情况,用4字节表示
else:
# 构造第1字节(11110xxx)
byte1 = 0b11110000 | (codepoint >> 18)
# 构造第2字节(10xxxxxx)
byte2 = 0b10000000 | ((codepoint >> 12) & 0b00111111)
# 构造第3字节(10xxxxxx)
byte3 = 0b10000000 | ((codepoint >> 6) & 0b00111111)
# 构造第4字节(10xxxxxx)
byte4 = 0b10000000 | (codepoint & 0b00111111)
# 返回组合后的4字节
return bytes([byte1, byte2, byte3, byte4])

# 定义函数,将UTF-8字节序列转换为Unicode码点
def utf8_to_unicode(utf8_bytes):
"""
将 UTF-8 字节序列转换为 Unicode 码点(反向转换)

参数:
utf8_bytes: UTF-8 编码的字节序列

返回:
int: Unicode 码点
"""
# 检查输入是否为空
if not utf8_bytes:
raise ValueError("空字节序列")

# 获取首字节
first_byte = utf8_bytes[0]

# 1字节编码情况:0xxxxxxx
if (first_byte & 0b10000000) == 0:
return first_byte

# 2字节编码情况:110xxxxx 10xxxxxx
elif (first_byte & 0b11100000) == 0b11000000:
# 检查字节长度是否充足
if len(utf8_bytes) < 2:
raise ValueError("不完整的 UTF-8 序列")
# 取高5位+低6位,拼出码点
codepoint = ((first_byte & 0b00011111) << 6) | \
(utf8_bytes[1] & 0b00111111)
return codepoint

# 3字节编码情况:1110xxxx 10xxxxxx 10xxxxxx
elif (first_byte & 0b11110000) == 0b11100000:
# 检查字节长度是否充足
if len(utf8_bytes) < 3:
raise ValueError("不完整的 UTF-8 序列")
# 拼接高4位、中间6位和低6位得码点
codepoint = ((first_byte & 0b00001111) << 12) | \
((utf8_bytes[1] & 0b00111111) << 6) | \
(utf8_bytes[2] & 0b00111111)
return codepoint

# 4字节编码情况:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
elif (first_byte & 0b11111000) == 0b11110000:
# 检查字节长度是否充足
if len(utf8_bytes) < 4:
raise ValueError("不完整的 UTF-8 序列")
# 拼接高3位、次高6位、次低6位和低6位得码点
codepoint = ((first_byte & 0b00000111) << 18) | \
((utf8_bytes[1] & 0b00111111) << 12) | \
((utf8_bytes[2] & 0b00111111) << 6) | \
(utf8_bytes[3] & 0b00111111)
return codepoint

# 非法的UTF-8首字节
else:
raise ValueError("无效的 UTF-8 首字节")

# 测试代码:定义要测试的字符列表
test_chars = ['A', '中', '😀']
# 遍历每个字符
for char in test_chars:
# 取出字符对应的Unicode码点
codepoint = ord(char)

# 手动将码点转为UTF-8字节序列
utf8_manual = unicode_to_utf8(codepoint)

# 用Python内置方法编码
utf8_builtin = char.encode('utf-8')

# 打印字符及转码信息
print(f"\n字符: '{char}'")
print(f" Unicode码点: U+{codepoint:04X} ({codepoint})")
print(f" UTF-8字节: {utf8_manual.hex().upper()}")
print(f" 字节数: {len(utf8_manual)}")
print(f" 验证: {'正确' if utf8_manual == utf8_builtin else '错误'}")

# 用自定义方法解码回码点
decoded = utf8_to_unicode(utf8_manual)
# 再转回字符
decoded_char = chr(decoded)
print(f" 反向解码: '{decoded_char}' (U+{decoded:04X})")

3.详细实例

3.1 实例1:ASCII字符 'A'(1字节)

步骤分解:

1️⃣ Unicode 码点
'A' = U+0041 = 65

2️⃣ 判断字节数
65 ≤ 127,使用 1字节编码

3️⃣ 二进制表示
65 = 0100 0001 (8位)

4️⃣ 应用编码格式
格式:0xxxxxxx
填充:0[1000001]

5️⃣ 结果
字节1: 01000001 = 0x41

最终结果: 'A' → 41

3.2 实例2:汉字 '中'(3字节)

步骤分解:


1️⃣ Unicode 码点
'中' = U+4E2D = 20013

2️⃣ 判断字节数
2048 ≤ 20013 ≤ 65535,使用 3字节编码

3️⃣ 二进制表示(16位)
0x4E2D = 0100 1110 0010 1101
↓ ↓ ↓ ↓
分组:0100 | 111000 | 101101
高4位 中6位 低6位

4️⃣ 应用编码格式
格式:1110xxxx 10xxxxxx 10xxxxxx

字节1: 1110[0100] = 11100100 = 0xE4
字节2: 10[111000] = 10111000 = 0xB8
字节3: 10[101101] = 10101101 = 0xAD

5️⃣ 位运算实现
高4位 = (0x4E2D >> 12) & 0xF = 0x4
中6位 = (0x4E2D >> 6) & 0x3F = 0x38
低6位 = 0x4E2D & 0x3F = 0x2D

字节1 = 0b11100000 | 0x4 = 0xE4
字节2 = 0b10000000 | 0x38 = 0xB8
字节3 = 0b10000000 | 0x2D = 0xAD

最终结果: '中' → E4 B8 AD

3.3 实例3:Emoji '😀'(4字节)

步骤分解:

1️⃣ Unicode 码点
'😀' = U+1F600 = 128512

2️⃣ 判断字节数
128512 > 65535,使用 4字节编码

3️⃣ 二进制表示(21位)
0x1F600 = 0001 1111 0110 0000 0000
↓ ↓ ↓ ↓ ↓
分组:000 | 011111 | 011000 | 000000
高3位 次高6位 次低6位 低6位

4️⃣ 应用编码格式
格式:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

字节1: 11110[000] = 11110000 = 0xF0
字节2: 10[011111] = 10011111 = 0x9F
字节3: 10[011000] = 10011000 = 0x98
字节4: 10[000000] = 10000000 = 0x80

5️⃣ 位运算实现
高3位 = (0x1F600 >> 18) & 0x7 = 0x0
次高6位 = (0x1F600 >> 12) & 0x3F = 0x1F
次低6位 = (0x1F600 >> 6) & 0x3F = 0x18
低6位 = 0x1F600 & 0x3F = 0x0

字节1 = 0b11110000 | 0x0 = 0xF0
字节2 = 0b10000000 | 0x1F = 0x9F
字节3 = 0b10000000 | 0x18 = 0x98
字节4 = 0b10000000 | 0x0 = 0x80

最终结果: '😀' → F0 9F 98 80

4.位运算

4.1 关键运算符

运算符说明示例
>>右移(取高位)0x4E2D >> 12 = 0x4
<<左移(放大)0x4 << 12 = 0x4000
&按位与(提取)0x4E2D & 0x3F = 0x2D
`\ `按位或(组合)

4.2 常用掩码

# 提取位的掩码
0b00111111 = 0x3F # 提取低6位
0b00011111 = 0x1F # 提取低5位
0b00001111 = 0x0F # 提取低4位
0b00000111 = 0x07 # 提取低3位


# 标识位前缀
0b10000000 = 0x80 # 后续字节前缀
0b11000000 = 0xC0 # 2字节首字节前缀
0b11100000 = 0xE0 # 3字节首字节前缀
0b11110000 = 0xF0 # 4字节首字节前缀

4.3 位运算示例

# 示例:处理 '中' (U+4E2D)
codepoint = 0x4E2D # 20013

# 提取各部分(3字节编码)
高4位 = (codepoint >> 12) & 0x0F # 右移12位,取低4位 → 0x4
中6位 = (codepoint >> 6) & 0x3F # 右移6位,取低6位 → 0x38
低6位 = codepoint & 0x3F # 直接取低6位 → 0x2D

# 组装 UTF-8 字节
字节1 = 0b11100000 | 高4位 # 0xE0 | 0x4 = 0xE4
字节2 = 0b10000000 | 中6位 # 0x80 | 0x38 = 0xB8
字节3 = 0b10000000 | 低6位 # 0x80 | 0x2D = 0xAD

print(f"{字节1:02X} {字节2:02X} {字节3:02X}") # E4 B8 AD

5. 总结

  1. Unicode 定义字符编号,UTF-8 定义存储方式
  2. UTF-8 是变长编码 (1-4字节)
  3. 根据码点范围选择字节数
  4. 首字节和后续字节有固定的标识位
  5. 使用位运算提取和组合数据位
  6. Python 内置 encode()/decode() 方法最简单