因为多数场景只需匹配固定词表,无需CRF或神经网络开销,词典分词以内存小、启动快、无依赖等优势适合轻量服务;但不处理歧义切分与未登录词回退。

为什么不用现成的分词库,而要自己写一个词典分词?
因为多数场景下你只需要匹配固定词表(比如商品名、地名、专有名词),不需要 CRF 或神经网络模型带来的开销和依赖。用 map[string]bool 或前缀树(Trie)做精确匹配,内存占用小、启动快、无外部依赖,适合嵌入 CLI 工具或轻量服务。
但要注意:纯词典匹配不处理歧义切分(比如“南京市长江大桥”可能切成“南京市/长江大桥”或“南京/市长/江大桥”),也不支持未登录词回退。如果你的业务允许“只认词典里有的,其余当单字”,那它就足够了。
怎么用 Trie 实现 O(1) 前缀查找?
Trie 是词典分词最常用的底层结构,比遍历 map 更省空间、更利于最长匹配。Golang 没有内置 Trie,但几行就能手写一个支持插入和最长前缀匹配的版本:
type TrieNode struct {
children map[rune]*TrieNode
isWord bool
}
<p>func (t <em>TrieNode) Insert(word string) {
node := t
for _, r := range word {
if node.children == nil {
node.children = make(map[rune]</em>TrieNode)
}
if node.children[r] == nil {
node.children[r] = &TrieNode{}
}
node = node.children[r]
}
node.isWord = true
}</p><p>func (t *TrieNode) LongestMatch(s string, start int) int {
node := t
end := start
for i := start; i < len(s); i++ {
r, size := utf8.DecodeRuneInString(s[i:])
if r == utf8.RuneError && size == 1 {
break
}
if node.children == nil || node.children[r] == nil {
break
}
node = node.children[r]
if node.isWord {
end = i + size
}
i += size - 1 // 跳过已解码的字节
}
return end
}关键点:
立即学习“go语言免费学习笔记(深入)”;
-
LongestMatch返回的是「能匹配上的最长词的右边界位置」,不是长度,调用时需传入当前扫描起点start - 必须用
utf8.DecodeRuneInString处理中文,不能直接按字节索引s[i],否则会乱码或 panic - 插入词时建议统一转小写或做标准化(如全角转半角),否则“北京”和“北京市”会被当作不同词
如何处理分词结果中的重叠与覆盖?
词典中常有包含关系:“上海”、“上海市”、“上海市浦东新区”。如果从左到右贪心匹配最长词,就自然解决覆盖问题;但如果想保留所有可能路径(比如用于 NER 后处理),就得改用 DAG 构建或回溯。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
实际项目中,95% 场景只需「最大正向匹配(MM)」,逻辑简单可靠:
- 从位置 0 开始,调用
Trie.LongestMatch(s, i) - 若返回 > i,说明匹配成功,切出
s[i:end],i 更新为end - 若没匹配上,取单个 Unicode 字符(
utf8.DecodeRuneInString(s[i:])),i += 对应字节数 - 重复直到
i >= len(s)
注意:utf8.RuneCountInString 返回字符数,len(s) 返回字节数——混用会导致越界或漏字。
加载词典时该用什么格式和编码?
推荐用 UTF-8 编码的纯文本,每行一个词,空行和以 # 开头的行自动跳过。避免用 JSON 或 YAML,增加解析负担且无必要。
加载时常见坑:
- 文件末尾带 BOM(尤其是 Windows 编辑器保存的),会导致首词开头多出
\ufeff,匹配失败 —— 用bytes.TrimPrefix(data, []byte("\xef\xbb\xbf"))清除 - 词含空白符(空格、制表符)没 trim,导致“苹果 ”和“苹果”查不到 —— 插入前务必
strings.TrimSpace - 词典过大(>10 万条)时,Trie 构建耗时明显,可考虑预编译成二进制 trie 结构,运行时 mmap 加载
真正难的不是实现,是词典质量本身:同义词要不要合并?缩写是否收录?“iPhone15”算不算中文词?这些得靠业务反馈持续迭代,代码只是执行层。

















