前言:目前本身在作使用Lucene.net和PanGu分词实现全文检索的工做,不过本身是把别人作好的项目进行迁移。由于项目总体要迁移到ASP.NET Core 2.0版本,而Lucene使用的版本是3.6.0 ,PanGu分词也是对应Lucene3.6.0版本的。不过好在Lucene.net 已经有了Core 2.0版本,4.8.0 bate版,而PanGu分词,目前有人正在作,貌似已经作完,只是尚未测试~,Lucene升级的改变我都会加粗表示。git
Lucene.net 4.8.0 github
https://github.com/apache/lucenenet数据库
PanGu分词(能够直接使用的)apache
https://github.com/SilentCC/Lucene.Net.Analysis.PanGuide
JIEba分词(能够直接使用的)函数
https://github.com/SilentCC/JIEba-netcore2.0工具
Lucene.net 4.8.0 和以前的Lucene.net 3.6.0 改动仍是至关多的,这里对本身开发过程遇到的问题,作一个记录吧,但愿能够帮到和我同样须要升级Lucene.net的人。我也是第一次接触Lucene ,也但愿能够帮助初学Lucene的同窗。测试
这里就对Lucene的Analyzer作一个简单的阐述,之后会对Analyzer作一个更加详细的笔记:Lucene 中的Analyzer 是一个分词器,具体的做用呢就是将文本(包括要写入索引的文档,和查询的条件)进行分词操做 Tokenization 获得一系列的分词 Token。咱们用的别的分词工具,好比PanGu分词,都是继承Analyzer 的,而且继承相关的类和覆写相关的方法。Analyzer 是怎么参与搜索的过程呢?this
咱们须要IndexWriter ,二IndexWriter 的构建 ,补充一下,Lucene3.6.0 的构造方法已经被抛弃了,新的构造方法是,依赖一个IndexWriterConfig 类,这记录的是IndexWriter 的各类属性和配置,这里不作细究了。IndexWriterConfig 的构造函数就要传入一个Analyzer .spa
IndexWriterConfig(Version matchVersion, Analyzer analyzer)
因此咱们写入索引的时候,会用到Analyzer , 写入的索引是这样一个借口,索引的储存方式是Document 类,一个Document类中有不少的Field (name, value)。咱们能够这样理解Document是是一个数据库中的表,Field是数据库的中的字段。好比一篇文章,咱们要把它存入索引,以便后来有人能够搜索到。
文章有不少属性:Title : xxx ; Author :xxxx;Content : xxxx;
document.Add(new Field("Title","Lucene")); document.Add(new Field("Author","dacc")); document.Add(new Field("Content","xxxxxx")); IndexWriter.AddDocument(document);
大抵是上面的过程,而分词器Analyzer须要作的就是Filed 的value进行分词,把很长的内容分红一个一个的小分词 Token。
咱们也须要Analyzer ,固然不是必须须要,和IndexWriter的必需要求不同。Analyzer的职责就是,将查询的内容进行分词,好比咱们查询的内容是 “全文检索和分词” ,那么Analyzer会把它先分解成“全文检索”和“分词”,而后在索引中,去找和有这些分词的Field ,而后把Field所在的Document,返回出去。这里搜索的细节在这里不细究了,之后也会作详细的笔记。
大概了解了Analyzer以后,我就列出我遇到的问题:
Object reference not set to an instance of an object
这个异常的意思是,引用了值为null的对象。因而我去翻找源码,发现
public TokenStream GetTokenStream(string fieldName, TextReader reader) { TokenStreamComponents components = reuseStrategy.GetReusableComponents(this, fieldName); TextReader r = InitReader(fieldName, reader); if (components == null) { components = CreateComponents(fieldName, r); reuseStrategy.SetReusableComponents(this, fieldName, components); } else { components.SetReader(r); } return components.TokenStream; }
在下面这条语句上面抛出了错误:
TokenStreamComponents components = reuseStrategy.GetReusableComponents(this, fieldName);
reuseStrategy 是一个空对象。因此这句就报错了。这里,咱们能够了解一下,Analyzer的内部.函数 GetTokenStream 是返回Analyzer中的TokenStream,TokenStream是一系列Token的集合。先不细究TokenStream的具体做用,由于会花不少的篇幅去说。而获取TokenStream 的关键就在reuseStrategy 。在新版本的Lucene中,Analyzer中TokenStream是能够重复使用的,即在一个线程中创建的Analyzer实例,都共用TokenStream。
internal DisposableThreadLocal<object> storedValue = new DisposableThreadLocal<object>();
Analyzer的成员 storedValue 是全局共用的,storedValue 中就储存了TokenStream 。而reuseStrategy也是Lucene3.6.0中没有的 的做用就是帮助实现,多个Analyzer实例共用storedValue 。ResuseStrategy类 中有成员函数GetReusableComponents 和SetReusableComponents 是设置TokenStream和Tokenizer的,
这是ResueStrategy类的源码,这个类是一个抽象类,Analyzer的内部类,
public abstract class ReuseStrategy { /// <summary> /// Gets the reusable <see cref="TokenStreamComponents"/> for the field with the given name. /// </summary> /// <param name="analyzer"> <see cref="Analyzer"/> from which to get the reused components. Use /// <see cref="GetStoredValue(Analyzer)"/> and <see cref="SetStoredValue(Analyzer, object)"/> /// to access the data on the <see cref="Analyzer"/>. </param> /// <param name="fieldName"> Name of the field whose reusable <see cref="TokenStreamComponents"/> /// are to be retrieved </param> /// <returns> Reusable <see cref="TokenStreamComponents"/> for the field, or <c>null</c> /// if there was no previous components for the field </returns> public abstract TokenStreamComponents GetReusableComponents(Analyzer analyzer, string fieldName); /// <summary> /// Stores the given <see cref="TokenStreamComponents"/> as the reusable components for the /// field with the give name. /// </summary> /// <param name="analyzer"> Analyzer </param> /// <param name="fieldName"> Name of the field whose <see cref="TokenStreamComponents"/> are being set </param> /// <param name="components"> <see cref="TokenStreamComponents"/> which are to be reused for the field </param> public abstract void SetReusableComponents(Analyzer analyzer, string fieldName, TokenStreamComponents components); /// <summary> /// Returns the currently stored value. /// </summary> /// <returns> Currently stored value or <c>null</c> if no value is stored </returns> /// <exception cref="ObjectDisposedException"> if the <see cref="Analyzer"/> is closed. </exception> protected internal object GetStoredValue(Analyzer analyzer) { if (analyzer.storedValue == null) { throw new ObjectDisposedException(this.GetType().GetTypeInfo().FullName, "this Analyzer is closed"); } return analyzer.storedValue.Get(); } /// <summary> /// Sets the stored value. /// </summary> /// <param name="analyzer"> Analyzer </param> /// <param name="storedValue"> Value to store </param> /// <exception cref="ObjectDisposedException"> if the <see cref="Analyzer"/> is closed. </exception> protected internal void SetStoredValue(Analyzer analyzer, object storedValue) { if (analyzer.storedValue == null) { throw new ObjectDisposedException("this Analyzer is closed"); } analyzer.storedValue.Set(storedValue); } }
Analyzer 中的另外一个内部类,继承了ReuseStrategy 抽象类。这两个类实现了设置Analyzer中的TokenStreamComponents和获取TokenStreamComponents 。这样的话Analyzer中GetTokenStream流程就清楚了
public sealed class GlobalReuseStrategy : ReuseStrategy { /// <summary> /// Sole constructor. (For invocation by subclass constructors, typically implicit.) </summary> [Obsolete("Don't create instances of this class, use Analyzer.GLOBAL_REUSE_STRATEGY")] public GlobalReuseStrategy() { } public override TokenStreamComponents GetReusableComponents(Analyzer analyzer, string fieldName) { return (TokenStreamComponents)GetStoredValue(analyzer); } public override void SetReusableComponents(Analyzer analyzer, string fieldName, TokenStreamComponents components) { SetStoredValue(analyzer, components); } }
另外呢Analyzer 也能够设置TokenStream:
public TokenStream GetTokenStream(string fieldName, TextReader reader) { //先获取上一次共用的TokenStreamComponents TokenStreamComponents components = reuseStrategy.GetReusableComponents(this, fieldName); TextReader r = InitReader(fieldName, reader); //若是没有,就须要本身建立一个 if (components == null) { components = CreateComponents(fieldName, r); //而且设置新的ResuableComponents,可让下一个使用 reuseStrategy.SetReusableComponents(this, fieldName, components); } else { //若是以前就生成过了,TokenStreamComponents,则reset components.SetReader(r); } //返回TokenStream return components.TokenStream; }
因此咱们在调用Analyzer的时候,Analyzer有一个构造函数
public Analyzer(ReuseStrategy reuseStrategy) { this.reuseStrategy = reuseStrategy; }
设置Analyzer 的 ReuseStrategy , 而后我发如今PanGu分词中,使用的构造函数中并无传入ReuseStrategy , 按咱们就须要本身建一个ReuseStrategy的实例。
PanGu分词的构造函数:
public PanGuAnalyzer(bool originalResult) : this(originalResult, null, null) { } public PanGuAnalyzer(MatchOptions options, MatchParameter parameters) : this(false, options, parameters) { } public PanGuAnalyzer(bool originalResult, MatchOptions options, MatchParameter parameters) : base() { this.Initialize(originalResult, options, parameters); } public PanGuAnalyzer(bool originalResult, MatchOptions options, MatchParameter parameters, ReuseStrategy reuseStrategy) : base(reuseStrategy) { this.Initialize(originalResult, options, parameters); } protected virtual void Initialize(bool originalResult, MatchOptions options, MatchParameter parameters) { _originalResult = originalResult; _options = options; _parameters = parameters; }
我调用的是第二个构造函数,结果传进去的ReuseStrategy 是null ,因此咱们须要新建实例,事实上Analyzer中已经为咱们提供了:
public static readonly ReuseStrategy GLOBAL_REUSE_STRATEGY = new GlobalReuseStrategy()
因此稍微改动一下PanGu分词的构造函数就好 了:
public PanGuAnalyzer(MatchOptions options, MatchParameter parameters) : this(false, options, parameters, Lucene.Net.Analysis.Analyzer.GLOBAL_REUSE_STRATEGY) { }