前言:目前自己在做使用 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 升级的改变我都会加粗表示。
Lucene.net 4.8.0
https://github.com/apache/lucenenet
PanGu 分词
https://github.com/LonghronShen/Lucene.Net.Analysis.PanGu/tree/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 是怎么参与搜索的过程呢?
我们需要 IndexWriter , 二 IndexWriter 的构建 ,补充一下,Lucene3.6.0 的构造方法已经被抛弃了,新的构造方法是,依赖一个 IndexWriterConfig 类,这记录的是 IndexWriter 的各种属性和配置,这里不做细究了。IndexWriterConfig 的构造函数就要传入一个 Analyzer .
- 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
- {
- ///
- /// Gets the reusable for the field with the given name.
- ///
- /// from which to get the reused components. Use
- /// and
- /// to access the data on the .
- /// Name of the field whose reusable
- /// are to be retrieved
- /// Reusable for the field, or null
- /// if there was no previous components for the field
- public abstract TokenStreamComponents GetReusableComponents(Analyzer analyzer, string fieldName);
- ///
- /// Stores the given as the reusable components for the
- /// field with the give name.
- ///
- /// Analyzer
- /// Name of the field whose are being set
- /// which are to be reused for the field
- public abstract void SetReusableComponents(Analyzer analyzer, string fieldName, TokenStreamComponents components);
- ///
- /// Returns the currently stored value.
- ///
- /// Currently stored value or null if no value is stored
- /// if the is closed.
- 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();
- }
- ///
- /// Sets the stored value.
- ///
- /// Analyzer
- /// Value to store
- /// if the is closed.
- 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
- {
- ///
- /// Sole constructor. (For invocation by subclass constructors, typically implicit.)
- [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)
- {
- }
来源: http://www.cnblogs.com/dacc123/p/8035438.html