2026/01/06

理解零寬字符:完整指南

了解零寬字符(ZWSP、ZWJ、ZWNJ、WJ)的一切——它們是什麼、如何工作、合法用途,以及為什麼它們會出現在AI生成的文本中。包含示例和檢測方法的完整指南。

你有沒有從ChatGPT或其他AI工具複製文本時注意到什麼奇怪的地方?也許你的代碼沒有按預期工作,或者正則表達式模式匹配失敗,即使文本看起來完全正常?你並不孤單。我也遇到過這種情況,花了一段時間才弄清楚發生了什麼。

罪魁禍首?零寬字符——不可見的Unicode字符,不佔用任何視覺空間,但可能引起各種問題。這些字符在Unicode標準中正式定義,由Unicode聯盟維護,它們在排版、語言學和文本處理中有合法用途。然而,它們也可以用於為AI生成的內容添加水印,這就是為什麼你可能在AI工具的文本中遇到它們。

什麼是零寬字符?

零寬字符是特殊的Unicode字符,具有零視覺寬度——意味著當你查看文本時它們不顯示任何內容,但它們仍然存在於字符序列中。可以把它們想像成不可見的標記,可以影響軟件如何處理、顯示或解釋文本。

這些字符是官方Unicode標準的一部分,這是文本編碼的國際標準。它們最初是為合法的排版和語言目的而設計的,例如:

  • 複雜腳本處理:阿拉伯語、波斯語和泰語等語言使用這些字符來正確渲染文本
  • 表情符號序列:將多個表情符號組合成果複雜序列(如家庭表情符號)
  • 排版控制:防止不需要的換行或控制文本流
  • 語言處理:處理沒有空格語言中的詞邊界

然而,因為它們是不可見的,並且可以在不影響外觀的情況下嵌入文本中,它們也被用於其他目的,包括為AI生成的內容添加水印。

零寬字符的類型

有幾種類型的零寬字符,每種都有其特定的用途和Unicode代碼點。讓我們分解最常見的幾種:

類型名稱Unicode描述常見用途
ZWSP零寬空格U+200B一個零寬度的不可見字符,在Unicode標準中定義為用於泰語等腳本中的單詞分隔。可能通過多種方式出現在文本中。泰語中的單詞分隔、水印、文本處理
ZWJ零寬連接符U+200D一個不打印的字符,在Unicode標準中定義為用於連接相鄰字符,常用於複雜腳本和表情符號序列(參見Unicode表情符號標準)。表情符號序列、複雜腳本、水印
ZWNJ零寬非連接符U+200C一個不可見字符,在Unicode標準中定義為用於防止相鄰字符連接,在排版中用於波斯語和阿拉伯語等腳本。波斯語/阿拉伯語排版、防止字符連接
WJ詞連接符U+2060一個不可見字符,在Unicode標準中定義為用於防止單詞之間的換行,確保文本保持在一起。防止換行、保持文本在一起

參考資料:所有這些字符都在Unicode標準中正式定義。有關詳細的技術規範,請參見Unicode字符數據庫Unicode技術報告

零寬空格(ZWSP)- U+200B

零寬空格可能是最常遇到的零寬字符,尤其是在AI生成的文本中。顧名思義,它是一個不可見的空格字符,不佔用任何視覺空間。

合法用途:

  • 泰語:用於泰語腳本中的單詞分隔,泰語不使用單詞之間的空格
  • 文本處理:可用於在文本處理系統中標記詞邊界
  • 換行:某些系統使用它來指示允許換行的位置

示例:

const text = "Hello\u200BWorld";
console.log(text.length); // 返回 11(包括不可見空格)
console.log(text === "HelloWorld"); // 返回 false!

為什麼它出現在AI文本中: AI服務可能插入ZWSP字符作為水印方案的一部分。由於它們是不可見的,它們不會影響閱讀體驗,但可以通過程序檢測到。

零寬連接符(ZWJ)- U+200D

零寬連接符用於將相鄰字符連接在一起,特別是在複雜腳本和表情符號序列中。它是在AI生成的文本中發現的最常見的零寬字符之一。

合法用途:

  • 表情符號序列:將多個表情符號組合成果複雜序列。例如,家庭表情符號 👨‍👩‍👧‍👦 是使用ZWJ連接單個表情符號創建的
  • 複雜腳本:用於阿拉伯語、波斯語和印度語腳本等語言,以控制字符連接
  • 連字:在某些書寫系統中創建連字

示例:

// 家庭表情符號使用ZWJ
const family = "👨\u200D👩\u200D👧\u200D👦";
console.log(family); // 顯示為單個家庭表情符號

為什麼它出現在AI文本中: ZWJ經常用於AI水印,因為它在合法文本中足夠常見(特別是與表情符號一起),不會引起懷疑,但仍然可以通過程序檢測到。

零寬非連接符(ZWNJ)- U+200C

零寬非連接符與ZWJ相反——它防止相鄰字符連接在一起。它主要用於字符通常連接的腳本,如阿拉伯語和波斯語。

合法用途:

  • 波斯語/阿拉伯語排版:防止波斯語和阿拉伯語文本中不需要的字符連接
  • 文本格式化:控制字符在某些上下文中的顯示方式
  • 語言處理:標記字符不應連接的邊界

示例:

// 在波斯語/阿拉伯語文本中,ZWNJ防止字符連接
const persianText = "مثال\u200Cمثال"; // 防止連接

為什麼它出現在AI文本中: 在AI水印中不如ZWJ或ZWSP常見,但仍被某些服務用作水印方案的一部分。

詞連接符(WJ)- U+2060

詞連接符用於防止單詞之間的換行,確保某些文本序列保持在同一行上。

合法用途:

  • 防止換行:保持像"price: $100"這樣的文本在一行上
  • 技術格式化:確保代碼片段、URL或技術術語不會尷尬地換行
  • 排版:在格式化文本中保持視覺一致性

示例:

const price = "price:\u2060$100";
// WJ防止"price:"和"$100"之間的換行

為什麼它出現在AI文本中: 在水印中使用頻率較低,但仍可能出現在AI生成的內容中,特別是在格式化或技術文本中。

零寬字符的合法用途

在我們深入探討為什麼這些字符出現在AI文本中之前,重要的是要了解它們有許多合法和重要的用途:

1. 複雜腳本渲染

阿拉伯語、波斯語、泰語和各種印度語腳本等語言依賴零寬字符來正確渲染文本。這些字符控制字母如何連接、單詞如何分隔以及文本如何視覺流動。

泰語示例:

// 泰語文本使用ZWSP進行單詞分隔
const thaiText = "สวัสดี\u200Bครับ"; // 泰語中的"Hello"

2. 表情符號序列

現代表情符號嚴重依賴ZWJ來創建複雜序列。沒有ZWJ,我們就不會有像這樣的表情符號:

  • 👨‍👩‍👧‍👦(家庭)
  • 👨‍💻(技術專家)
  • 🏳️‍🌈(彩虹旗)

工作原理:

// 家庭表情符號是通過使用ZWJ連接單個表情符號創建的
const family = "👨\u200D👩\u200D👧\u200D👦";

3. 排版和文本格式化

零寬字符有助於控制文本流、防止不需要的換行並保持格式化一致性。這在以下方面特別重要:

  • 技術文檔
  • 代碼示例
  • 具有特定佈局要求的格式化文本

4. 文本處理和NLP

在自然語言處理和文本分析中,零寬字符可以標記詞邊界、指示特殊格式化或提供有關文本結構的元數據。

為什麼零寬字符出現在AI生成的文本中

現在,這就是有趣的地方。雖然零寬字符有合法用途,但它們也被AI服務用於水印。原因如下:

水印和內容追蹤

AI公司可能將零寬字符插入到它們生成的文本中作為水印的一種形式。這有幾個目的:

內容歸屬:通過嵌入不可見標記,AI服務可以追蹤它們生成的內容最終去了哪裡。這有助於它們了解使用模式和內容分發。

檢測:水印允許AI服務(和其他人)在野外檢測AI生成的內容。隨著AI生成的內容變得越來越普遍,這一點變得越來越重要。

研究和改進:追蹤AI生成內容的使用方式有助於公司改進其模型並了解實際使用模式。

法律和合規:水印可以幫助版權和內容所有權追蹤,隨著AI生成的內容變得更加普遍,這一點很重要。

水印辯論

值得注意的是,使用零寬字符進行水印是一個持續研究和辯論的主題。雖然一些AI服務可能使用這些字符進行水印,但重要的是要了解:

  • 並非所有零寬字符都是水印:這些字符可能由於複製粘貼操作、瀏覽器渲染、文本處理管道或合法的排版需求而出現
  • 檢測不是確定的:零寬字符的存在並不能明確證明它們是由AI服務插入的
  • 存在其他水印方法:一些AI服務使用統計水印(單詞選擇模式)而不是字符插入

然而,無論它們的來源如何,這些不可見字符都可能給開發者和內容創作者帶來真正的問題。

如何檢測零寬字符

如果你懷疑你的文本包含零寬字符,有幾種方法可以檢測它們:

方法1:在瀏覽器控制台中使用JavaScript

檢查零寬字符最簡單的方法是在瀏覽器控制台中使用JavaScript:

// 檢測所有零寬字符的函數
function detectZeroWidth(text) {
    const zeroWidthChars = {
        'ZWSP': '\u200B',  // 零寬空格
        'ZWJ': '\u200D',   // 零寬連接符
        'ZWNJ': '\u200C',  // 零寬非連接符
        'WJ': '\u2060'     // 詞連接符
    };

    const results = {};

    for (const [name, char] of Object.entries(zeroWidthChars)) {
        const count = (text.match(new RegExp(char, 'g')) || []).length;
        if (count > 0) {
            results[name] = count;
        }
    }

    return results;
}

// 用法
const text = "你的文本";
const detected = detectZeroWidth(text);
console.log('檢測到的零寬字符:', detected);

方法2:使用Python

Python可以輕鬆檢測和計數零寬字符:

def detect_zero_width(text):
    """檢測文本中的零寬字符"""
    zero_width_chars = {
        'ZWSP': '\u200B',  # 零寬空格
        'ZWJ': '\u200D',   # 零寬連接符
        'ZWNJ': '\u200C',  # 零寬非連接符
        'WJ': '\u2060'     # 詞連接符
    }

    results = {}
    for name, char in zero_width_chars.items():
        count = text.count(char)
        if count > 0:
            results[name] = count

    return results

# 用法
text = "你的文本"
detected = detect_zero_width(text)
print(f"檢測到的零寬字符: {detected}")

方法3:使用在線Unicode分析器

有几个在線工具可以帮助你可视化和檢測零寬字符:

方法4:使用文本編輯器

许多代碼編輯器有擴展或內置功能来顯示零寬字符:

VS Code:

  • 安装"Zero Width Characters"擴展
  • 或使用內置的"Render Whitespace"功能(虽然它可能不会顯示所有零寬字符)

Sublime Text:

  • 使用"Unicode Character Highlighter"插件
  • 或在视图设置中启用"Show All Characters"

Vim:

  • 使用:set list顯示不可见字符
  • 配置listchars以顯示零寬字符

Notepad++:

  • 从"视图"菜單启用"Show All Characters"
  • 零寬字符可能顯示为特殊符号

零寬字符引起的问题

尽管这些字符是不可见的,但它們可能在各种場景中引起真正的问题:

1. 字符串長度不匹配

零寬字符在字符串長度中被计数,这可能导致意外行为:

const text = "Hello\u200BWorld";
console.log(text.length); // 返回 11,而不是 10
console.log(text === "HelloWorld"); // 返回 false!

// 这可能破坏驗證
if (text.length === 10) {
    // 这永远不会执行,因为長度是 11
}

2. 正则表達式模式失败

正则表達式可能无法匹配包含零寬字符的文本:

// 如果有零寬字符,这个正则表達式不会匹配
const pattern = /^HelloWorld$/;
const text = "Hello\u200BWorld";
console.log(pattern.test(text)); // 返回 false!

// 即使有词边界
const wordPattern = /\bHello\b/;
const text2 = "Hello\u200BWorld";
console.log(wordPattern.test(text2)); // 可能返回 false

3. 數據庫存儲问题

某些數據庫系統不能很好地處理零寬字符:

  • 編碼錯誤:较旧的SQL數據庫可能抛出編碼錯誤
  • 搜索失败:查詢不会匹配包含隐藏字符的文本
  • 索引損壞:某些數據庫系統在索引中使用这些字符时可能有问题
  • 存儲開銷:虽然很小,但这些字符确实占用空间

4. API集成问题

许多API期望没有特殊Unicode字符的干净文本:

// API驗證可能失败
const apiData = {
    username: "user\u200Bname",
    // 某些API会拒绝这个
};

// JSON解析通常没问题,但驗證可能失败
fetch('/api/user', {
    method: 'POST',
    body: JSON.stringify(apiData)
});

5. 代碼和编程问题

在代碼中使用AI生成的文本时,零寬字符可能破坏:

  • 代碼註釋:可能导致解析问题
  • 字符串字面量:可能破坏字符串匹配
  • 配置文件:可能导致解析錯誤
  • 模板字符串:可能破坏模板處理

6. 內容管理系統

某些CMS平台会剥离或錯誤處理零寬字符:

  • 文本截斷:字符可能被计数但不顯示,导致截斷问题
  • 格式化丢失:可能干擾文本格式化
  • 顯示问题:可能导致前端渲染问题
  • 搜索功能:可能破坏搜索功能

7. 文本處理和分析

零寬字符可能干擾:

  • 單詞计数:可能影响單詞计数的準確性
  • 文本分析:可能干擾NLP工具
  • 抄襲檢測:可能导致假阳性或假阴性
  • 文本比較:可能破坏文本差異工具

真实世界示例

让我分享一些零寬字符引起问题的真实場景:

示例1:表單驗證失败

// 用戶将AI生成的文本粘貼到表單中
const username = "john\u200Bdoe"; // 包含ZWSP

// 驗證检查長度
if (username.length > 8) {
    showError("用戶名太长");
    // 即使看起来像8个字符,这也会觸發
}

// 數據庫查詢失败
db.query("SELECT * FROM users WHERE username = ?", [username]);
// 找不到匹配,因为數據庫中的"johndoe"没有ZWSP

示例2:電子郵件解析问题

// 包含零寬字符的電子郵件地址
const email = "user\u200B@example.com";

// 電子郵件驗證
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
console.log(emailRegex.test(email)); // 可能返回 false

// 電子郵件發送失败
sendEmail(email, "主題", "正文");

示例3:URL處理

// 包含零寬字符的URL
const url = "https://example.com/page\u200B1";

// URL驗證
try {
    new URL(url); // 可能抛出錯誤或創建無效URL
} catch (e) {
    console.error("無效URL");
}

// 获取失败
fetch(url); // 請求失败

如何移除零寬字符

如果你在文本中檢測到零寬字符并想要移除它們,你有几个選擇:

方法1:使用我们的清理工具

最简单的方法是使用我们的**水印清理工具**。它专门为此目的而设计,可以處理所有类型的零寬字符:

  1. 将文本粘貼到工具中
  2. 點擊"清理文本"
  3. 複製清理后的结果

该工具完全在瀏覽器中本地處理所有內容——不会向任何服務器發送數據,确保完全隱私。

方法2:JavaScript函數

你可以創建一个简单的JavaScript函數来移除零寬字符:

function removeZeroWidth(text) {
    return text
        .replace(/\u200B/g, '')  // 零寬空格
        .replace(/\u200D/g, '')  // 零寬連接符
        .replace(/\u200C/g, '')  // 零寬非連接符
        .replace(/\u2060/g, ''); // 词連接符
}

// 用法
const cleaned = removeZeroWidth("Hello\u200BWorld");
console.log(cleaned); // "HelloWorld"

或使用单个正则表達式:

function removeZeroWidth(text) {
    return text.replace(/[\u200B-\u200D\u2060]/g, '');
}

方法3:Python函數

在Python中,你可以这样移除零寬字符:

import re

def remove_zero_width(text):
    """从文本中移除零寬字符"""
    # 移除所有零寬字符
    return re.sub(r'[\u200B-\u200D\u2060]', '', text)

# 用法
text = "Hello\u200BWorld"
cleaned = remove_zero_width(text)
print(cleaned)  # "HelloWorld"

方法4:使用庫

有几个庫可以帮助處理Unicode字符:

JavaScript:

  • unorm - Unicode規範化
  • punycode - 編碼/解碼

Python:

  • unicodedata - 內置Unicode數據庫
  • unidecode - ASCII音譯

最佳实践

以下是一些處理零寬字符的最佳实践:

1. 始终清理用戶输入

如果你接受来自用戶的文本输入(特别是如果它可能来自AI工具),在處理之前清理它:

function cleanUserInput(input) {
    // 移除零寬字符
    return input.replace(/[\u200B-\u200D\u2060]/g, '');
}

2. 存儲前驗證

在将文本存儲到數據庫之前清理它:

function sanitizeForDatabase(text) {
    return text
        .replace(/[\u200B-\u200D\u2060]/g, '') // 移除零寬字符
        .trim(); // 移除前导/尾随空白
}

3. 小心處理表情符號

记住某些表情符號合法使用ZWJ。如果你正在移除零寬字符,你可能会破坏表情符號序列:

// 这个表情符號使用ZWJ - 移除它会破坏它
const family = "👨\u200D👩\u200D👧\u200D👦";
const broken = family.replace(/\u200D/g, ''); // 破坏表情符號

考虑在表情符號上下文中保留ZWJ,或者至少意识到这个限制。

4. 记录檢測

如果你正在清理文本,考虑在檢測到零寬字符时记录:

function cleanAndLog(text) {
    const before = text.length;
    const cleaned = text.replace(/[\u200B-\u200D\u2060]/g, '');
    const after = cleaned.length;

    if (before !== after) {
        console.warn(`移除了 ${before - after} 个零寬字符`);
    }

    return cleaned;
}

5. 测试你的代碼

始终使用包含零寬字符的文本测试你的代碼:

// 测试用例
const testCases = [
    "Hello\u200BWorld",
    "Test\u200DString",
    "Normal text"
];

testCases.forEach(text => {
    const cleaned = removeZeroWidth(text);
    console.assert(cleaned.length <= text.length, "清理不应增加長度");
});

常见问题(FAQ)

以下是一些关于零寬字符的常见问题:

问:零寬字符总是水印吗?

不,不一定。零寬字符有许多合法用途:

  • 表情符號序列(家庭表情符號等)
  • 複雜脚本渲染(阿拉伯语、波斯语、泰语)
  • 排版和文本格式化
  • 文本處理和NLP

它們也可能由于以下原因出現:

  • 複製粘貼操作
  • 瀏覽器渲染
  • 文本處理管道
  • 字体渲染

零寬字符的存在并不能明确证明它們是由AI服务插入的。

问:移除零寬字符会破坏我的文本吗?

通常不会,但有例外:

  • 表情符號序列:从表情符號序列中移除ZWJ会破坏它們(例如,👨‍👩‍👧‍👦变成独立的表情符號)
  • 複雜脚本:从阿拉伯语、波斯语或泰语文本中移除零寬字符可能影响渲染
  • 格式化文本:在某些情况下可能影响文本流或格式化

对于大多数英语文本和代碼,移除零寬字符是安全的。

问:我怎么知道我的文本是否有零寬字符?

你可以:

  1. 使用上面描述的檢測方法(JavaScript、Python、在線工具)
  2. 使用我们的**水印清理工具** - 它会顯示是否檢測到任何字符
  3. 在代碼編輯器中检查,使用适当的擴展
  4. 使用Unicode分析工具

问:零寬字符有害吗?

在安全意义上不是有害的,但它們可能导致:

  • 代碼錯誤和失败
  • 數據庫问题
  • API集成问题
  • 文本處理錯誤
  • 格式化问题

它們更像是烦恼而不是安全威胁,但它們肯定会导致问题。

问:我可以防止零寬字符被插入吗?

如果你自己生成文本,你可以避免插入它們。但是,如果你从AI服务或其他来源接收文本,你无法防止它們被插入——但你可以檢測并移除它們。

问:所有AI服务都使用零寬字符进行水印吗?

不。不同的AI服务使用不同的方法:

  • 一些使用零寬字符
  • 一些使用統計水印(單詞選擇模式)
  • 一些使用语义水印
  • 一些可能根本不使用水印

使用零寬字符进行水印并未被大多数AI服务正式记录。

问:移除零寬字符合法吗?

这取决于你使用的AI服务的服务条款。一般来说,移除不可见的跟踪字符类似于从网站移除cookie或跟踪像素。但是,你应该:

  • 查看你使用的AI工具的服务条款
  • 如果你有疑虑,咨询法律顾问
  • 考虑道德影响

问:移除零寬字符会使AI文本无法檢測吗?

不一定。移除零寬字符只移除一种潜在的檢測方法。高级AI檢測系統可能使用:

  • 写作模式的統計分析
  • 词汇和句子结构分析
  • 语义分析
  • 其他隐写方法

移除零寬字符有帮助,但不能保证无法檢測。

其他资源

如果你想深入了解零寬字符和Unicode,以下是一些权威资源:

总结

零寬字符既迷人又複雜。它們在排版、语言学和文本處理中服务于合法目的,但当它們意外出現在AI生成的文本或其他来源中时,也可能导致问题。

了解它們是什麼、如何檢測它們以及如何處理它們对于任何从事文本處理工作的人来说都是必不可少的,特别是在AI生成內容的时代。无论你是處理代碼的開發者、使用AI工具的內容创作者,还是只是对文本工作原理感到好奇的人,了解零寬字符可以为你节省很多麻烦。

如果你在文本中遇到零寬字符并想要清理它們,试试我们的水印清理工具 →。它是免费的,完全在瀏覽器中工作,可以處理所有常见的零寬字符类型。

记住:这些字符本质上并不坏——它們是可用於好或有问题目的的工具。关键是理解它們并知道如何有效地使用它們。


← 返回首页