Lucene.net(4.8.0)+PanGu分词器问题记录一:分词器Analyzer的构造和内部成员ReuseStategy
前言:目前自己在做使用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/SilentCC/Lucene.Net.Analysis.PanGu
JIEba分词(可以直接使用的)
https://github.com/SilentCC/JIEba-netcore2.0
Lucene.net 4.8.0 和之前的Lucene.net 3.6.0 改动还是相当多的,这里对自己开发过程遇到的问题,做一个记录吧,希望可以帮到和我一样需要升级Lucene.net的人。我也是第一次接触Lucene ,也希望可以帮助初学Lucene的同学。
一,Lucene 分词器:Analyzer
这里就对Lucene的Analyzer做一个简单的阐述,以后会对Analyzer做一个更加详细的笔记:Lucene 中的Analyzer 是一个分词器,具体的作用呢就是将文本(包括要写入索引的文档,和查询的条件)进行分词操作 Tokenization 得到一系列的分词 Token。我们用的别的分词工具,比如PanGu分词,都是继承Analyzer 的,并且继承相关的类和覆写相关的方法。Analyzer 是怎么参与搜索的过程呢?
1.在写入索引的时候:
我们需要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。
2.在查询搜索的时候,
我们也需要Analyzer ,当然不是必须需要,和IndexWriter的必须要求不一样。Analyzer的职责就是,将查询的内容进行分词,比如我们查询的内容是 “全文检索和分词” ,那么Analyzer会把它先分解成“全文检索”和“分词”,然后在索引中,去找和有这些分词的Field ,然后把Field所在的Document,返回出去。这里搜索的细节在这里不细究了,以后也会做详细的笔记。
二,问题:
大概了解了Analyzer之后,我就列出我遇到的问题:
1.在调用Analyer的GetTokenStream 之后,抛出
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)
{
}
Lucene.net(4.8.0)+PanGu分词器问题记录一:分词器Analyzer的构造和内部成员ReuseStategy的更多相关文章
- Lucene.net(4.8.0) 学习问题记录一:分词器Analyzer的构造和内部成员ReuseStategy
前言:目前自己在做使用Lucene.net和PanGu分词实现全文检索的工作,不过自己是把别人做好的项目进行迁移.因为项目整体要迁移到ASP.NET Core 2.0版本,而Lucene使用的版本是3 ...
- Lucene.net(4.8.0) 学习问题记录五: JIEba分词和Lucene的结合,以及对分词器的思考
前言:目前自己在做使用Lucene.net和PanGu分词实现全文检索的工作,不过自己是把别人做好的项目进行迁移.因为项目整体要迁移到ASP.NET Core 2.0版本,而Lucene使用的版本是3 ...
- Lucene.net(4.8.0) 学习问题记录二: 分词器Analyzer中的TokenStream和AttributeSource
前言:目前自己在做使用Lucene.net和PanGu分词实现全文检索的工作,不过自己是把别人做好的项目进行迁移.因为项目整体要迁移到ASP.NET Core 2.0版本,而Lucene使用的版本是3 ...
- Lucene 03 - 什么是分词器 + 使用IK中文分词器
目录 1 分词器概述 1.1 分词器简介 1.2 分词器的使用 1.3 中文分词器 1.3.1 中文分词器简介 1.3.2 Lucene提供的中文分词器 1.3.3 第三方中文分词器 2 IK分词器的 ...
- Lucene系列四:Lucene提供的分词器、IKAnalyze中文分词器集成、扩展 IKAnalyzer的停用词和新词
一.Lucene提供的分词器StandardAnalyzer和SmartChineseAnalyzer 1.新建一个测试Lucene提供的分词器的maven项目LuceneAnalyzer 2. 在p ...
- lucene全文搜索之二:创建索引器(创建IKAnalyzer分词器和索引目录管理)基于lucene5.5.3
前言: lucene全文搜索之一中讲解了lucene开发搜索服务的基本结构,本章将会讲解如何创建索引器.管理索引目录和中文分词器的使用. 包括标准分词器,IKAnalyzer分词器以及两种索引目录的创 ...
- Lucene.net(4.8.0) 学习问题记录三: 索引的创建 IndexWriter 和索引速度的优化
前言:目前自己在做使用Lucene.net和PanGu分词实现全文检索的工作,不过自己是把别人做好的项目进行迁移.因为项目整体要迁移到ASP.NET Core 2.0版本,而Lucene使用的版本是3 ...
- Lucene.net(4.8.0) 学习问题记录四: IndexWriter 索引的优化以及思考
前言:目前自己在做使用Lucene.net和PanGu分词实现全文检索的工作,不过自己是把别人做好的项目进行迁移.因为项目整体要迁移到ASP.NET Core 2.0版本,而Lucene使用的版本是3 ...
- Lucene.net(4.8.0) 学习问题记录六:Lucene 的索引系统和搜索过程分析
前言:目前自己在做使用Lucene.net和PanGu分词实现全文检索的工作,不过自己是把别人做好的项目进行迁移.因为项目整体要迁移到ASP.NET Core 2.0版本,而Lucene使用的版本是3 ...
随机推荐
- linux C 文件操作之fgets()
1. fgets(...)从标准设备读数据. 原型:fgets(s,n,stdin); 假设在控制台下,我们可以用fgets(...)替代gets(),读入键盘输入的信息,fget ...
- Python-week1,第一周(基于Python3.0以上)
1,变量 准确来说不是第一周学习了吧,应该是采用博客记录学习的第一周,记录并做个笔记吧,可能做的不好,但我高兴啊,废话不说了,上图. 学习过程中做的一些笔记,当然能面面俱到,只能在写博客的时候又能复习 ...
- Linux系列教程(十三)——Linux软件包管理之源码包、脚本安装包
上篇博客我们讲解了网络yum源和光盘yum源的搭建步骤,然后详细介绍了相关的yum命令,yum 最重要是解决了软件包依赖性问题.在安装软件时,我们使用yum命令将会简单方便很多.我们知道yum命令只能 ...
- 蓝桥杯-算法训练--ALGO-8 操作格子
问题描述 有n个格子,从左到右放成一排,编号为1-n. 共有m次操作,有3种操作类型: 1.修改一个格子的权值, 2.求连续一段格子权值和, 3.求连续一段格子的最大值. 对于每个2.3操作输出你所求 ...
- 蓝桥杯-算法训练--ALGO-6 安慰奶牛
问题描述Farmer John变得非常懒,他不想再继续维护供奶牛之间供通行的道路.道路被用来连接N个牧场,牧场被连续地编号为1到N.每一个牧场都是一个奶牛的家.FJ计划除去P条道路中尽可能多的道路,但 ...
- 开发一个基于 Android系统车载智能APP
很久之前就想做一个车载相关的app.需要实现如下功能: (1)每0.2秒更新一次当前车辆的最新速度值. (2)可控制性记录行驶里程. (3)不连接网络情况下获取当前车辆位置.如(北京市X区X路X号) ...
- SQL Server分组查询某最大值的整条数据(包含linq写法)
想实现如下效果,就是分组后时间最大的那一条数据: 1.SQL SELECT * FROM ( SELECT * , ROW_NUMBER() OVER ( PARTITION BY RIP_GUID ...
- 前端基于react,后端基于.net core2.0的开发之路(2) 开发环境的配置,注意事项,后端数据初始化
前端环境配置 项目介绍文章:前端基于react,后端基于.net core2.0的开发之路(1) 介绍 1.VSCode安装 下载地址:https://code.visualstudio.com/Do ...
- 极简版ASP.NET Core学习路径及教程
绝承认这是一个七天速成教程,即使有这个效果,我也不愿意接受这个名字.嗯. 这个路径分为两块: 实践入门 理论延伸 有了ASP.NET以及C#的知识以及项目经验,我们几乎可以不再需要了解任何新的知识就开 ...
- 基础教程:上传/下载ASP.NET Core 2.0中的文件
问题 如何上传和下载ASP.NET Core MVC中的文件. 解 在一个空的项目中,更新 Startup 类以添加MVC的服务和中间件. publicvoid ConfigureServices( ...