“幽灵字符”问题
某个位置的字符被替换成了视觉上相同、但底层编码不同的字符
全角(Full-width):每个字符占用两个标准字符位置,常用于中文标点、汉字以及部分英文字符和符号。全角字符在视觉上更宽,适合中文排版,保证文字对齐整齐。
半角(Half-width):每个字符占用一个标准字符位置,主要用于英文字符、数字和西文标点。半角字符在程序代码、英文文档或不支持中文的系统中使用较多。
既然长度相等且肉眼看起来一样,说明不是多了一个隐藏字符(否则长度会不同),而是某个位置的字符被替换成了视觉上相同、但底层编码不同的字符。
以下是导致这种现象最常见的 4 个原因及排查方法:
1. 全角与半角字符混淆(最常见)
肉眼看起来都是空格或字母,但一个是半角(ASCII),一个是全角(中文输入法下打出的)。
半角空格: ' ' (Unicode: U+0020)
全角空格: ' ' (Unicode: U+3000)
全角字母: A (Unicode: U+FF21) vs 半角 A (Unicode: U+0041)
2. 零宽字符(Zero-width characters)
这些字符在屏幕上不占宽度、不可见,但它们是真实的字符,会占用字符串长度。
零宽空格 (U+200B)
零宽非连接符 (U+200C)
零宽连接符 (U+200D)
场景:从网页、PDF、微信复制文本时,经常会自动插入这些字符用于排版或防复制。
3. Unicode 规范化问题(NFC vs NFD)
这在处理带声调的字母(如法语、西班牙语)或Emoji时极易发生。同一个视觉符号,底层有两种存储方式:
组合形式 (NFC): é 作为一个单独的字符 (U+00E9)
分解形式 (NFD): e (U+0065) + ́ (组合重音符号 U+0301)
结果:肉眼看都是 é,长度也都是 1(或对应比例),但 == 比较为 False。macOS 的文件系统默认使用 NFD,而 Linux/Windows 多用 NFC,跨平台传数据时极易踩坑。
4. 相似字形字符(Homoglyphs)
不同语言或不同区块中,长得一模一样的字符:
西里尔字母 а (U+0430) vs 拉丁字母 a (U+0061)
数字 0 (U+FF10) vs 数字 0 (U+0030)
零宽空格
| Unicode | 名称 | 说明 |
|---|---|---|
U+200B |
零宽空格 (ZWSP) | 最常见的零宽字符,用于指示可换行点,从网页/PDF复制文本时极易混入 |
U+200C |
零宽非连接符 (ZWNJ) | 阻止相邻字符连写,常见于阿拉伯语、波斯语排版 |
U+200D |
零宽连接符 (ZWJ) | 强制相邻字符连写,Emoji 组合(如 👨👩👧👦)就是靠它实现的 |
U+2060 |
单词连接符 (Word Joiner) | 类似零宽空格,但禁止在该位置换行 |
U+FEFF |
零宽无断空格 / BOM 标记 | 文件开头标识编码格式(UTF-8 BOM = EF BB BF),也可能出现在字符串首尾 |
文本方向控制
| Unicode | 名称 | 说明 |
|---|---|---|
U+200E |
左至右标记 (LRM) | 强制后续文本从左到右排列 |
U+200F |
右至左标记 (RLM) | 强制后续文本从右到左排列 |
U+202A |
左至右嵌入 (LRE) | 嵌套的从左到右方向控制 |
U+202B |
右至左嵌入 (RLE) | 嵌套的从右到左方向控制 |
U+202C |
弹出方向格式 (PDF) | 结束上述方向嵌套 |
U+202D |
左至右覆盖 (LRO) | 强制从左到右覆盖方向 |
U+202E |
右至左覆盖 (RLO) | 强制从右到左覆盖方向(常被用于文件名伪装攻击) |
其他
| Unicode | 名称 | 说明 |
|---|---|---|
U+00AD |
软连字符 (Soft Hyphen) | 仅在断行时显示为连字符,平时不可见 |
U+034F |
组合图形连接符 (CGJ) | 不可见,用于影响组合字符的处理 |
U+061C |
阿拉伯字母标记 (ALM) | 影响阿拉伯语排版方向 |
U+180E |
蒙古文元音分隔符 | 在蒙古文中不可见,但影响排版 |
清楚零宽字符
import re
def remove_zero_width(text):
"""移除所有零宽字符和不可见控制字符"""
return re.sub(r'[\u200B-\u200F\u202A-\u202E\u2060\uFEFF\u00AD\u034F\u061C\u180E]', '', text)
# 使用示例
str1 = "hello" # 看起来是 hello
str2 = "hello" # 真正的 hello
print(str1 == str2) # False!
print(remove_zero_width(str1) == str2) # True

浙公网安备 33010602011771号