Logo
Published on

2.4.Unicode2UTF8

Authors
  • avatar
    Name
    xiaobai
    Twitter

1.基础概念

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+007F1字节0xxxxxxx7位
U+0080 ~ U+07FF2字节110xxxxx 10xxxxxx11位
U+0800 ~ U+FFFF3字节1110xxxx 10xxxxxx 10xxxxxx16位
U+10000 ~ U+10FFFF4字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxx21位

2.2.规则说明

  1. 首字节标识:通过首字节的前几位判断字节数
    • 0xxxxxxx:1字节(ASCII)
    • 110xxxxx:2字节的首字节
    • 1110xxxx:3字节的首字节
    • 11110xxx:4字节的首字节
  2. 后续字节标识:所有后续字节都以 10 开头
    • 这样可以区分首字节和后续字节
  3. 数据位填充x 表示实际数据位
    • 将 Unicode 码点的二进制从低位到高位依次填入

3.以下内容不用看,Python全部学完后再看

4.算法实现

4.1.方法1:Python 内置方法

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

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

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

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

4.2.方法2:实现原理

def unicode_to_utf8(codepoint):
    """
    将 Unicode 码点转换为 UTF-8 字节序列
    
    参数:
        codepoint: Unicode 码点(整数)
    
    返回:
        bytes: UTF-8 编码的字节序列
    """
    # 检查有效范围
    if codepoint < 0 or codepoint > 0x10FFFF:
        raise ValueError(f"无效的 Unicode 码点: {codepoint}")
    
    # 1字节编码:0-127(ASCII 范围)
    if codepoint <= 0x7F:
        # 格式:0xxxxxxx
        return bytes([codepoint])
    
    # 2字节编码:128-2047
    elif codepoint <= 0x7FF:
        # 格式:110xxxxx 10xxxxxx
        # 第1字节:110 + 高5位
        byte1 = 0b11000000 | (codepoint >> 6)
        # 第2字节:10 + 低6位
        byte2 = 0b10000000 | (codepoint & 0b00111111)
        return bytes([byte1, byte2])
    
    # 3字节编码:2048-65535
    elif codepoint <= 0xFFFF:
        # 格式:1110xxxx 10xxxxxx 10xxxxxx
        # 第1字节:1110 + 高4位
        byte1 = 0b11100000 | (codepoint >> 12)
        # 第2字节:10 + 中间6位
        byte2 = 0b10000000 | ((codepoint >> 6) & 0b00111111)
        # 第3字节:10 + 低6位
        byte3 = 0b10000000 | (codepoint & 0b00111111)
        return bytes([byte1, byte2, byte3])
    
    # 4字节编码:65536-1114111
    else:
        # 格式:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
        # 第1字节:11110 + 高3位
        byte1 = 0b11110000 | (codepoint >> 18)
        # 第2字节:10 + 次高6位
        byte2 = 0b10000000 | ((codepoint >> 12) & 0b00111111)
        # 第3字节:10 + 次低6位
        byte3 = 0b10000000 | ((codepoint >> 6) & 0b00111111)
        # 第4字节:10 + 低6位
        byte4 = 0b10000000 | (codepoint & 0b00111111)
        return bytes([byte1, byte2, byte3, byte4])


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
    
    else:
        raise ValueError("无效的 UTF-8 首字节")


"""测试转换函数"""
test_chars = ['A', '中', '😀']

print("=" * 60)
print("Unicode ↔ UTF-8 转换测试")
print("=" * 60)

for char in test_chars:
    # 获取 Unicode 码点
    codepoint = ord(char)
    
    # 手动转换
    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})")

5.详细实例

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

步骤分解:


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

2️⃣ 判断字节数
   65127,使用 1字节编码

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

4️⃣ 应用编码格式
   格式:0xxxxxxx
   填充:0[1000001]
   
5️⃣ 结果
   字节1: 01000001 = 0x41

最终结果: 'A'41

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

步骤分解:


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

2️⃣ 判断字节数
   20482001365535,使用 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

5.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

6.位运算

6.1.关键运算符

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

6.2.常用掩码

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

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

6.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

7.常见字符编码表

字符名称UnicodeUTF-8 字节字节数
A拉丁字母U+0041411
0数字U+0030301
¥人民币符号U+00A5C2 A52
欧元符号U+20ACE2 82 AC3
汉字U+4E2DE4 B8 AD3
汉字U+6587E6 96 873
👍点赞U+1F44DF0 9F 91 8D4
😀笑脸U+1F600F0 9F 98 804

8.记忆技巧

8.1.口诀

UTF-8 编码不难记,
看码点范围选字节:

01271字节,
ASCII 直接不变化;

12820472字节,
110 开头加 10 跟;

2048655353字节,
1110 开头两个 10
65536 以上用4字节,
11110 开头三个 10
数据位按序填入,
位运算移位取值!

8.2.识别技巧

看首字节判断字节数

  • 0xxxxxxx → 1字节
  • 110xxxxx → 2字节
  • 1110xxxx → 3字节
  • 11110xxx → 4字节

后续字节统一

  • 都是 10xxxxxx 格式

9.常见问题

9.1.UTF-8 和 Unicode 有什么区别?

A: Unicode 是字符集(定义了字符的编号),UTF-8 是编码方式(定义了如何存储这些编号)。

9.2.为什么有些字符是3字节,有些是4字节?

A: 根据 Unicode 码点的大小决定。码点越大,需要的字节数越多。

9.3.UTF-8 和 UTF-16 哪个更好?

A:

  • UTF-8:英文占用少,网络传输常用
  • UTF-16:中文占用少(固定2字节),Windows 内部使用

9.4.如何判断文件是 UTF-8 编码?

def is_utf8(data):
    """检查字节序列是否为有效的 UTF-8"""
    try:
        data.decode('utf-8')
        return True
    except UnicodeDecodeError:
        return False

10.总结

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