Lucene.net(4.8.0) 学习问题记录二: 分词器Analyzer中的TokenStream和AttributeSource[通俗易懂]

Lucene.net(4.8.0) 学习问题记录二: 分词器Analyzer中的TokenStream和AttributeSource[通俗易懂]前言:目前自己在做使用Lucene.net和PanGu分词实现全文检索的工作,不过自己是把别人做好的项目进行迁移。因为项目整体要迁移到ASP.NETCore2.0版本,而Lucene使用的版本是3.6.0,PanGu分词也是对应Lucene3.6.0版本的。不过好在Lucene.net已经有了Core2.0版本,4.8.0bate版,而PanGu分词,目前有人正在做,貌似已经做完,只是…

大家好,又见面了,我是你们的朋友全栈君。

前言:目前自己在做使用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的同学。

 

一,Analyzer 中的TokenStream 

1.TokenSteam的产生

在这篇博文中,其实已经介绍了TokenStream 是怎么产生的:

 http://www.cnblogs.com/dacc123/p/8035438.html

在Analyzer 中,同一个线程上的所有Analyzer实例都是共用一个TokenStream,而实现如此都是因为Analyzer类中 storedValue 是全局共用的,获取TokenStream的方法是由reuseStrategy 类提供的,TokenStream 继承自AttributeSource

那么TokenStream的作用什么呢?

2.TokenSteam的使用

TokenStream 实际上是由一系列Token(分词)组合起来的序列,这里仅仅介绍如何通过TokenStream获得分词的信息。TokenStream的工作流程:

    1. 创建TokenStream

    2.TokenStream.Reset()

    3.TokenStream.IncrementToken()

    4.TokenStream.End();

    5.TokenStream.Dispose() //Lucene 4.8.0中已经取消了Close(),只有Dispose()

在执行:

_indexWriter.AddDocument(doc)

之后,IndexWriter则会调用初始化时创建的Analyzer,也即IndewWriterConfig()中的Analyzer参数。这里以PanGu分词为例子。

调用分词器,首先会执行CreateComponents()函数,创建一个TokenStreamComponents,这也是为什么所有自定义,或者外部的分词器如果继承Analyzer,必须要覆写CreateComponents()函数:

  protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
        {
            var result = new PanGuTokenizer(reader, _originalResult, _options, _parameters);
            var finalStream = (TokenStream)new LowerCaseFilter(LVERSION.LUCENE_48, result);

          
            finalStream.AddAttribute<ICharTermAttribute>();
            finalStream.AddAttribute<IOffsetAttribute>();

            return new TokenStreamComponents(result, finalStream);
        }

可以看到在这个CreateComponents函数中,我们可以初始化创建自己想要的Tokenizer和TokenStream。TokenStreamComponents是Lucene4.0中才有的,一个TokenStreamComponents是由Tokenizer和TokenStream组成。

在初始化完TokenStream 之后我们可以添加属性Attribute 到TokenStream中:

finalStream.AddAttribute<ICharTermAttribute>();
finalStream.AddAttribute<IOffsetAttribute>();

  2.1 AttributeSource的介绍

  上面说到TokenStream 继承自AttributeSource , finalStream.AddAttribute<ICharTermAttribute> 真是调用了父类AttributeSource的方法AddAttribute<T>() ,所以AttributeSoucre是用来给TokenStream添加一系列属性的,这是Lucene4.8.0中AttributeSource中AddAttribute的源码:

  

  public T AddAttribute<T>()
            where T : IAttribute
        {
            var attClass = typeof(T);
            if (!attributes.ContainsKey(attClass))
            {
                if (!(attClass.GetTypeInfo().IsInterface && typeof(IAttribute).IsAssignableFrom(attClass)))
                {
                    throw new ArgumentException("AddAttribute() only accepts an interface that extends IAttribute, but " + attClass.FullName + " does not fulfil this contract.");
                }
          //正真添加Attribute的函数,而创造Attribute实例则是通过AttributeSource中的 
          //private readonly AttributeFactory factory; AddAttributeImpl(
this.factory.CreateAttributeInstance<T>()); } T returnAttr; try { returnAttr = (T)(IAttribute)attributes[attClass].Value; } #pragma warning disable 168 catch (KeyNotFoundException knf) #pragma warning restore 168 { return default(T); } return returnAttr; }

 2.2 Attribute介绍

    上面介绍了AttributeSource 给TokenStream添加属性Attribute ,其实Attribute就是你需要获得的分词的属性。

    比如:上面写到的 ICharTermAttribute 继承自CharTermAttribute 表示的是分词内容;

       IOffsetAttribute 继承自 OffsetAttribute 表示的是分词起始位置和结束位置;

    类似的还有 IFlasAttribute , IKeywordAttribute,IPayloadAttribute,IPositionIncrementAttribute,IPositionLengthAttribute,ITermToBytesRefAttribute,ITypeAttribute

    我们再看Token(分词)类的源码:

    

  public class Token : CharTermAttribute, ITypeAttribute, IPositionIncrementAttribute, IFlagsAttribute, IOffsetAttribute, IPayloadAttribute, IPositionLengthAttribute

   

    其实Token(分词),是继承这些Attribute,也就是说分词是由这些属性组成的,所以就可以理解为什么在TokenStream中添加Attributes。

    

再回到之前,再初始化TokenStream 和添加完属性之后,必须执行TokenStream的Reset(),才可继续执行TokenStream.IncrementToken().

Reset()函数实际上在TokenStream创建和使用之后进行重置,因为我们之前说过,在Analyzer中所有实例是共用一个TokenStream的所以在TokenStream被使用过一次后,需要Reset() 以清除上次使用的信息,重新给下一个需要分词的text使用。

而IncrementToken实际的作用则是在遍历TokenStream 中的Token,类似于一个迭代器。

  public sealed override bool IncrementToken()
        {
            ClearAttributes();
            Token word = Next();
            if (word != null)
            {
                var buffer = word.ToString();
                termAtt.SetEmpty().Append(buffer);
                offsetAtt.SetOffset(word.StartOffset, word.EndOffset);
                typeAtt.Type = word.Type;
                return true;
            }
            End();
            this.Dispose();
            return false;
        }

直到返回的false ,表示分词已经遍历完了,这个时候调用End() 和Dispose() 来注销这个TokenStream。在这个过程中,TokenStream是可以被使用多次的,比如我写入索引的时候,加入两个Field : 

new Field("title","xxxx")
new Field("content","xxxxx")

对这个两个域进行分词,TokenStream创建之后,会先对title进行分词,遍历。然后执行Reset(),再对content进行分词,遍历。直到所有要分词的域都遍历过了。才会执行End()和Dispose()函数进行销毁。

 

二,问题:搜索不到内容

  在迁移的过程中,突然出现了搜索不到内容的bug,经过调试,发现写索引的时候,对文本的分词都是正确。这里要提一点,分词(Token) 和 Term的区别 ,term是最小的搜索的单位,就是每个词语,比如“我是搞IT的”,那么,经过分词 “我”,“是”,“搞”,“IT” 这些都是term,而这些分词的具体信息,比如起始位置信息,都包含在Token当中,在Lucene2.9中之后,已经不推荐用Token(分词),而直接用Attribute表示这些term的属性 

      后来发现写索引的时候正常,但是在搜索的时候,获取搜索关键词是,利用自己写的TokenStream获取分词信息出了错。

  

    tokenStream.Reset();
            //ItermAttribute在Lucene4.8.0中已经替换为CharTermAttribute
            while (tokenStream.IncrementToken())
            {
                
                var termAttr = tokenStream.GetAttribute<ICharTermAttribute>();
                var str = new string(termAttr.Buffer, 0, termAttr.Buffer.Length);
                var positionAttr = tokenStream.GetAttribute<IOffsetAttribute>();
                var start = positionAttr.StartOffset;
                var end = positionAttr.EndOffset;
                yield return new Token() { EndPosition = end, StartPosition = start, Term = str };
            }
           

termAttr.Buffer  是字节数组,而termAttr.Buffer.Length 是字节数组的长度,是固定。而termAttr.Length 是字节数组中实际元素的长度,是不一样的。我那样写会导致得到term字节信息是 [69,5b,23,/0,/0,/0,/0,/0,/0,/0] 因为长度填错了,所以后面自动填充/0,这样自然搜索不到,改成termAttr.Length就可以了。

这里在提一下在Lcuene.net 4.0中新增了BytesRef 类,表示term的字节信息,以后会介绍道

 

转载于:https://www.cnblogs.com/dacc123/p/8118526.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/163114.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Go适合做什么?为何这么多人偏爱Go语言?

    Go作为Google2009年推出的语言,其被设计成一门应用于搭载Web服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。对于高性能分布式系统领域而言,Go语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。到现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。哪些大公司在使用Go语言:1、Goog…

    2022年4月6日
    75
  • 我的世界服务器开启显示坐标的指令,我的世界坐标怎么看 坐标指令「建议收藏」

    我的世界服务器开启显示坐标的指令,我的世界坐标怎么看 坐标指令「建议收藏」在我的世界游戏中,坐标这个问题如果玩的很6的话能够帮助我们瞬间移动,去到任何想去的地方,这时有不少玩家不禁想问了,我的世界坐标怎么看,坐标指令又是什么呢?坐标(coordinates)在数字上反映了您在主世界中的位置。坐标基于一个由三条交于一点(即原点)的坐标轴而形成的网格。玩家会出生在距离原点数百方块的位置上。x轴反映了玩家距离原点在东()西(-)方向上的距离,如经度。z轴反映了玩家距离原点在…

    2022年9月24日
    3
  • .tex文件中通过空行实现LaTeX换行输出

    .tex文件中通过空行实现LaTeX换行输出【LaTeX换行输出代码示例】\documentclass{article}\begin{document} Happy\TeXing. Hello\LaTeX. Happy\TeXing. Hello\LaTeX.\end{document}【输出结果】

    2022年5月14日
    45
  • Windows Server 2012 R2/2016 此工作站和主域间的信任关系失败[通俗易懂]

    Windows Server 2012 R2/2016 此工作站和主域间的信任关系失败[通俗易懂]今天给客户Exchange服务器出现了脱域的情况,当使用域帐户登录时出现了“此工作站和主域间的信任关系失败”的情况。造成这种的可能原因:域内存在了多台SID一样的计算机;计算机对象在AD中意外删除;客户端的帐户密码更新失败;时间超过5分钟;AD复制问题等等;计算机登录现象:解决方法:首先确认在ActiveDirectory的ComputersOU(其他OU也可以)中存在该计算机对象;使用服务器的本地管理员(.\administrator)登录计算机;使用本地管理…

    2022年10月19日
    3
  • windows搭建svn_pokemmo中文语言包

    windows搭建svn_pokemmo中文语言包上一篇文章讲了如何下载安装SVN,装的原生的SVN,并没有进行相应的配置,这里进行配置SVN官方语言包还没有安装的移步安装教程——>Windows搭建SVN实现访问远程SVN库首先我们打开svn下载界面(点击可进入)往下翻即可看到语言包,这是各个国家不同的语言包,我使用的是中文的语言包,选择中文简体(第一个)下载完成,点击开始安装吧妥妥的下一步啊喏,直接安装完成,勾选下面,配置svn使用此语言包【注意,勾选,勾选,勾选】(没有勾选?成吧,继续往下…

    2025年8月28日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号