最近在做一个邮件系统,需要解析邮件内容,然后搜索相应的数据库内容,进行邮件回复。
不难想象,在此过程中,除了邮件收发之外,还有分词、全文检索等功能需要实现。
稍微花了点时间,搜了一下,免费的.Net分词比较少...
全文检索更加,除了数据库方式的全文检索外,我看到的是满屏的Luceue。
我之前就有对Luceue头痛过,除了操作有点多之外,我仍然不能很好地理解它处理索引文件的方式(惭愧)
....
之后决定自己做一个小型的分词和全文索引的类库吧
首先是分词
看了蛮多的,都推荐 最大匹配法 分词,意思就是大词优先匹配切分
代码片段如下:
//分词函数,使用预载好的“字典”信息逐词匹配
public string Parse(string source) {
//max 为最大词长,
int max = MaxWordLength > source.Length ? source.Length : MaxWordLength;
//_wordSet是一个集合,用于存储分出来的词,同时避免了重复词
//清除上次缓存
wordSet.Clear();
//多设置一个string,为了备份,因为优先匹配出大词后删除,避免小词再次匹配
//如 "程序员" 能同时匹配 "程序员" 和 "程序"
string word = source;
//大词优先,所以为--step
for (int step = max; step >= MinWordLength; --step) {
for (int i = 0; i <= word.Length - step; ++i) {
string subword = word.Substring(i, step);
if (WordsDictionary.ContainsKey(subword)) {
_wordSet.Add(WordsDictionary[subword]);
word = word.Replace(subword, "");
//这个操作时为了演示,就是在匹配出的词的前后加上'/'分隔符
source = source.MarkWith(subword, '/');
}
}
}
//返回划分好的句子
return source;
}
算法比较简单,这个代码片段也只是演示而已,可能有错漏之处,仅供借鉴
分词中另一个棘手的事情就是词库,也就是“字典”
我使用的字典是 Java分词工具 庖丁解牛(Paoding)的基础词库
内含24万常用词汇(t-base.dic 2.4M)
我不太喜欢用文件存储,所以自己写了个小程序导入了数据库
用数据库的话,就比较方便管理,比文件的合并和拆分好用
下来是全文索引
全文索引就是预先为资源中出现的关键字设定好资源的标识
只要分词 把搜索词 匹配出关键字,那么就能直接取出相对应的资源来
比如我自定义的格式如下:
15030|10,9,4,5,8
12372|1
155|10,4,6,7
122|1,2,4
11111|2,5,8,9
18888|1,23,9
...
其中"|"之前的是关键字的数据库主键Id,之后的是有该关键字的资源的数据库主键Id
这样就很明了了
比如 搜索词为:“程序员写程序”
分词为:“程序员/写/程序”
假设关键字“程序员”的数据库Id为 15030
那么即刻可以取出对应的资源为
Id为 4,5,8,9,10 的资源来
在全文索引上面,我选择了文件方式。
因为涉及的数据都是比较零散索引Id,在数据库中使用varchar或者text就发挥不出数据库的快速
但是如果用 int,就要涉及多表
代码片段:
public class IndexData {
private HashSet<int> _index;
...
public int Id { get; set; }
...
public HashSet<int> Indexs {
get { return _index; }
}
//从文件行初始化
public IndexData(string indexData) : this() {
string[] datas = indexData.Split('|');
Id = Convert.ToInt32(datas[0]);
string[] indexIds = datas[1].Split(',');
foreach(string indexId in indexIds) {
_index.Add(Convert.ToInt32(indexId));
}
}
public bool IsContainIndex(int index) {
...
}
public void AddIndex(int index) {
...
}
public void RemoveIndex(int index) {
...
}
public override bool Equals(object obj) {
...
}
public override int GetHashCode() {
...
}
//重写ToString方法,方便写入文件
public override string ToString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(Id).Append("|");
foreach(int index in _index) {
stringBuilder.Append(index).Append(",");
}
stringBuilder.Remove(stringBuilder.Length - 1, 1);
return stringBuilder.ToString();
}
}
}
上面代码所示的是一条索引记录的实体
其中,构造函数从文件行得到实体,ToString()方法从实体转换为文件行
这里只提供一个小测试,纯做参考
词库:庖丁base词库 24万词
词库容器:数据库Sql Server 2005
词库预读:有,Dictionary
测试搜索词:以前总觉得,自己花了那么多的精力去学一门语言,到头来,被N多人说不是,觉得很委屈,血气方刚者,早已经破口大骂了
分词结果:以前/总/觉得,自己/花了/那么多/的/精力/去学一门/语言,到头来,被N多人说/不是,/觉得/很/委屈,/血气方刚/者,早/已经/破口大骂/了
耗时:152ms
符合资源条数:1
虽然分词并不是很准确,但是…
算是一种努力尝试吧,毕竟也有很多的地方可以优化…
如果大家有什么好的想法,欢迎讨论!