盘古分词在 Lucene.net 2.9 版本下搜索没有结果这个问题已经被发现很长一段时间了,前阵子一直忙着搞HubbleDotNet,没顾上这个,最近打算对盘古分词进行升级,添加一些功能进去,顺便就去看看这个问题。
用Lucene.net 2.9 版本替换盘古分词原来用的2.3.1 版本后,首先发现很多接口方法都过时了,这些方法3.0版本后将从 lucene 的源码中删除,lucene 这种和应用通过方法调用链接的紧密耦合设计我个人认为是会降低应用系统的可维护性的,每次升级lucene.net 要同时对调用代码重新修改编译,索引文件也不能移植,要重新索引,这些都对系统的维护带来很大的问题。这也是我为什么会在设计HubbleDotNet时采用SQL语句作为应用和索引组件之间的交互接口的原因。全文索引往往只是应用系统的一个附加功能,索引组件和应用系统松耦合设计可以大大提高应用系统的可维护性和可扩展性。扯远了,回到主题。
刚开始,我以为是这些过时方法造成的,修改后发现还是不行,后来发现Lucene.net 2.9 版本给的Demo中居然也用了一些过时的方法,看来问题不在这里。
我查找了 盘古分词问题解答专用贴 里一些网友的反馈记录,其中在2010年4月15日,LuckyMN 曾经有如下的反馈
@eaglet
string QueryStr = this.textBox1.Text.Trim();
// IndexSearcher search = new IndexSearcher(indexDir);
var q = GetKeyWordsSplitBySpace(QueryStr, new PanGuTokenizer());
QueryParser queryParser = new QueryParser("contents", new PanGuAnalyzer(true));
Query query = queryParser.Parse(q);
q:我^243.0 query:^243.0
我使用的lucene 版本是2.9.1.2
当我把版本换成demo中的2.3版本时候 query:我^243.0又正确了
我测了一下,确实有这个问题,其根本问题是出在 PanGuTokenizer 这个类里面
_InputText = input.ReadToEnd(); 这一句得到的_InputText 为空,
public PanGuTokenizer(System.IO.TextReader input) : base(input) { lock (_LockObj) { InitPanGuSegment(); } _InputText = input.ReadToEnd();
但为什么 2.3.1 版本没有问题呢,我一时也找不到答案,从上面这个调用代码看似乎很简单,我跟踪了一下,在执行 input.ReadToEnd(); 这一句前input 里面是有数据的,为什么ReadToEnd 却读不出数据呢?根据我的开发经验,我直觉认为很可能是 input 这个 TextReader 绑定的那个流的读指针的位置发生变化了,ReadToEnd 这个方法可能不是从流的起始位置开始读的。还好Lucene.net 是开源的,于是我跟踪了一下代码,终于真相大白。
原来问题出在 base(input) 这里面,Lucene.net 的基类的构造函数做了如下处理
/// <summary>Construct a token stream processing the given input. </summary> protected internal Tokenizer(System.IO.TextReader input) { this.input = CharReader.Get(input); }
CharReader.Get 则是这样的
public static CharStream Get(System.IO.TextReader input) { if (input is CharStream) return (CharStream) input; else { // {{Aroush-2.9}} isn't there a better (faster) way to do this? System.IO.MemoryStream theString = new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(input.ReadToEnd())); return new CharReader(new System.IO.StreamReader(theString)); } //return input is CharStream?(CharStream) input:new CharReader(input); }
也就是说在我执行 _InputText = input.ReadToEnd(); 之前,input 绑定的流已经被读了一次,读取位置已经发生了变化,为什么要这么做,我猜测可能是2.9版本对输入的字符串要做一个前缀合法性的检查。不管怎么样问题找到了,基类把流读到了一个 CharReader 中,即其基类的 input 成员。
于是我对代码进行了如下修改
public PanGuTokenizer(System.IO.TextReader input) : base(input) { lock (_LockObj) { InitPanGuSegment(); } _InputText = base.input.ReadToEnd();
修改后一切正常,以前听说2.9版本不支持多元分词,这次专门也测试了一下,没有问题。
顺带说一下盘古分词近期要加入的功能
盘古分词近期打算出一个 2.0 版本,要做的改进如下:
1. 对分词效率进行优化,提高分词速度,这个已经改了(1.3版本),目前的分词速度已经从原来320K 字符每秒提高到将近 500K 字符每秒。
2. 加入英文分词,英文分词主要是实现英文分解的词根化。
3. 增加对字典中以数字开头的专业非中文词汇的识别
4. 增加同义词分解的功能
5. 增加用户自定义规则的接口,可以让用户针对一些特殊的词汇,比如一些产品类型之类的,根据用户自己的规则进行分解。
6. 发布一个经过整理的新的字典,这个字典是由火车头采集器的作者提供给我的,我还没来得及整理,先对他表示感谢!
在出2.0 版本之前我会出几个中间版本,这些版本只有源码,没有编译好的文件,要下载源码可以到 盘古分词主页 的 Souce Code 中下载。
对Lucene.net 2.9 版本的支持的改动已经上传,1.3.1 版本以后的盘古分词都可以支持 Lucene.net 2.9 版本。