🎯 课程主题
掌握 CharacterTextSplitter —— 基于字符数进行文档切分的核心子类,理解其参数含义及 separator 优先切分机制。
📝 核心知识点
CharacterTextSplitter 概述
- 继承自 TextSplitter 父类
- 核心思想:按指定字符数 + 分隔符优先的方式进行切分
- 与 TextSplitter 相比,额外定义了
separator参数来影响切分行为
核心参数(继承 + 自有)
| 参数 | 默认值 | 说明 |
|---|---|---|
chunk_size | 4000 | chunk 的最大字符数 |
chunk_overlap | 200 | chunk 之间的重叠字符数 |
separator | "\n\n" | 分隔符(CharacterTextSplitter 特有),切分时优先按此分隔符切 |
length_function | len | 计算文本长度的函数 |
注意:
chunk_size和chunk_overlap的单位是字符(characters),在源码中明确注释为"overlap in characters between chunks"。
separator 的优先切分机制
如果不指定
separator(或设为空字符串""),则:- 只按
chunk_size进行切分 chunk_overlap正常生效- 可能在任意位置切断(可能破坏语义)
- 只按
如果指定了
separator(如"。"),则:- 优先按分隔符切分,保持语义完整性
- 分隔符切分后的片段如果小于
chunk_size,相邻片段会合并 - 如果某一片段大于
chunk_size,不会进一步切分,chunk_overlap也失效
切分的两个环节
CharacterTextSplitter 的内部处理分为两步:
第一步:拆分(Split) → 按 separator 将文本拆成多个片段
第二步:合并(Merge) → 将相邻的小片段合并(不超过 chunk_size)
| 场景 | 拆分 | 合并 | overlap 是否生效 |
|---|---|---|---|
| 片段 < chunk_size | 按 separator 拆分 | 相邻片段合并 | 生效 |
| 片段 > chunk_size | 按 separator 拆分 | 不合并不切 | 失效 |
| 无 separator | 按 chunk_size 硬切 | 无需合并 | 生效 |
🏗️ 架构与工作流
💻 代码实战
示例一:基础用法(无 separator,仅按字符数切分)
from langchain.text_splitter import CharacterTextSplitter
text = "这是一个关于LangChain框架的详细教程。里面包含了大量的示例代码。欢迎大家学习。"
splitter = CharacterTextSplitter(
chunk_size=50,
chunk_overlap=5,
separator="" # 空字符串 = 禁用分隔符优先
)
chunks = splitter.split_text(text)
for i, chunk in enumerate(chunks):
print(f"--- Chunk {i+1} (len={len(chunk)}) ---")
print(chunk)
# 输出:按约 50 字符切分,相邻 chunk 有 5 字符重叠
# Chunk 1 长度约 49(避免切断汉字)
# Chunk 2 长度约 49
示例二:指定 separator 优先切分
text = "第一段落的内容。第二段落的内容较多较长比较丰富的内容介绍。第三段。"
splitter = CharacterTextSplitter(
chunk_size=30,
chunk_overlap=5,
separator="。" # 优先按句号切分
)
chunks = splitter.split_text(text)
for i, chunk in enumerate(chunks):
print(f"--- Chunk {i+1} (len={len(chunk)}) ---")
print(chunk)
# 按句号拆分为 3 段:
# - "第一段落的内容。"(9 字符)
# - "第二段落的内容较多较长比较丰富的内容介绍。"(33 字符,> 30,不继续切)
# - "第三段。"(7 字符)
#
# 注意:第二段长度 33 > chunk_size=30,但不会被进一步切分!
# 此时 chunk_overlap 也对该段失效!
示例三:片段合并的场景
text = "第一部分的内容。" \
"第二部分的内容介绍了。" \
"第三部分。"
splitter = CharacterTextSplitter(
chunk_size=50,
chunk_overlap=5,
separator="。"
)
chunks = splitter.split_text(text)
for i, chunk in enumerate(chunks):
print(f"--- Chunk {i+1} (len={len(chunk)}) ---")
print(chunk)
# 拆分后 3 段长度都很小:
# 前两段合并后 < 50,所以合并为一个 chunk
# 第三段单独保留
# 此时 overlap 生效
示例四:配合 Document 使用(实际场景)
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
loader = TextLoader("./document.txt")
documents = loader.load()
splitter = CharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separator="\n"
)
# 使用 split_documents(方法三)
split_docs = splitter.split_documents(documents)
print(f"共切分为 {len(split_docs)} 个 chunk")
⚠️ 常见问题与避坑指南
separator 导致 chunk 超限:如果按分隔符切分后的某个片段超过了
chunk_size,CharacterTextSplitter 不会自动再切分,导致该 chunk 实际大小超过设定值。需要夸张例子验证时,将chunk_size设得很小即可看到效果。separator 为空字符串 ≠ 不指定 separator:
separator=""表示禁用分隔符优先,完全按chunk_size硬切- 不传
separator参数则使用默认值"\n\n"(按段落切分) - 两者行为不同,请按需选择
chunk_overlap 失效条件:只有按 separator 切分后所有片段都 ≤
chunk_size时,overlap 才生效。一旦存在超限片段,overlap 对该片段失效。汉字不会从中间切断:按字符数切分时,不会把一个汉字劈成两半。实际 chunk 长度可能略小于
chunk_size。适用场景限制:CharacterTextSplitter 只有一个分隔符,对于需要多级分隔符(段落 → 换行 → 空格 → 字符)的复杂文本,应使用 RecursiveCharacterTextSplitter。
💡 个人总结与延伸
- CharacterTextSplitter 的定位:它是一个简单直接的切分器,核心价值在于
separator优先 + 合并机制。适合结构简单、分隔符明确的文本(如按句号切分中文文章)。 - separator 带来的权衡:设置
separator可以保持语义完整性,但代价是可能产生超过chunk_size的 chunk。这在向量检索中可能导致某些 chunk 向量表示不够精细。 - 合并逻辑的意义:拆分后对小片段进行合并,避免产生过多碎片化的 chunk,使得每个 chunk 的信息密度更高,检索效果更好。
- 与 RecursiveCharacterTextSplitter 的关系:CharacterTextSplitter 只有一层分隔符,而 RecursiveCharacterTextSplitter 支持多级分隔符递归切分,是更常用、更健壮的方案。理解了 CharacterTextSplitter 的拆分+合并两阶段机制,就能更顺利地理解下一节课的 RecursiveCharacterTextSplitter。