最近在做一个邮件系统,需要解析邮件内容,然后搜索相应的数据库内容,进行邮件回复。
不难想象,在此过程中,除了邮件收发之外,还有分词、全文检索等功能需要实现。
稍微花了点时间,搜了一下,免费的.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
虽然分词并不是很准确,但是…
算是一种努力尝试吧,毕竟也有很多的地方可以优化…
如果大家有什么好的想法,欢迎讨论!