热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Lucene中的Tokenizer,TokenFilter学习

lucene中的TokenStream,TokenFilter之间关系TokenStream是一个能够在被调用后产
 
lucene中的TokenStream,TokenFilter之间关系
 
TokenStream是一个能够在被调用后产生语汇单元序列的类,其中有两个类型:Tokenizer和TokenFilter,两者的不同在于TokenFilter中包含了一个TokenStream作为input,该input仍然可以为一种TokenFilter进行递归封装,是一种组合模式;而Tokenzier接受一个Reader对象读取字符并创建语汇单元,TokenFilter负责处理输入的语汇单元,通过新增、删除或者修改属性的方式来产生新的语汇单元。
 


 
 
 
对照我们之前分析的同义词TokenizerFactory相关配置,其数据流的过程如下:
 
java.io.Reader -> com.chenlb.mmseg4j.solr.MMSegTokenizer -> SynonymFilter -> StopFilter -> WordDelimiterFilter -> LowerCaseFilter -> RemoveDuplicatesTokenFilter
 
 
对于某些TokenFilter来说,在分析过程中对事件的处理顺序非常重要。当指定过滤操作顺序时,还应该考虑这样的安排对于应用程序性能可能造成的影响。
 
在solr中,schema.xml(最新版本已经修改为managed-schema)的作用是告诉solr该如何对输入的文档进行索引。
 
http://www.liaozhida.net/solr/solr%E7%B3%BB%E5%88%97%E4%B8%83%E8%AF%A6%E8%A7%A3schema-xml%E7%89%B9%E6%80%A7.html
 
对于每个不同的field,需要设置其对应的数据类型,数据类型决定了solr如何去解释每个字段,以及怎样才能搜索到这个字段。在字段分析器中(field analyzers),指导solr怎样对输入的数据进行处理然后再构建出索引,类似于文本处理器或者文本消化器。
 
当一个document被索引或者检索操作的时候,分析器Analyzer会审阅字段field的文本内容,然后生成一个token流,analyzer可以由多个tokenizer和filter组成;tokenizer可以将field字段的内容切割成单个词或token,进行分词处理;filters可以接收tokenizer分词输出的token流,进行转化过滤处理,例如对词元进行转换(简繁体转换),舍弃无用词元(虚词谓词)。tokenizer和filter一起组成一个管道或者链条,对输入的文档和输入的查询文本进行处理,一系列的tokenizer和filter被称为分词器analyzer,得到的结果被存储成为索引字典用来匹配查询输入条件。
 
此外,我们还可以将索引分析器和查询分析器分开,例如下面的字段配置的意思:对于索引,先经过一个基本的分析器,然后转换为小写字母,接着过滤掉不在keepword.txt中的词,最后将剩下的词元转换为同义词;对于查询,先经过一个基本的分词器,然后转换为小写字母就可以了。
 

  
    
    
    
    
  
  
    
    
  
 
 
在Lucene实战一书中,详解了如何从头编写一个同义词Analyzer,通过改写termAttribute以及positionIncrementAttribute的方式来达到实现同义词的方式,不过由于书上的示例比较陈旧,而charTermAttribute不能达到修改同义词元的目的(只能进行append),因此替换最终的目的没有达到。
 
 
public class SynonymFilter extends TokenFilter {

    private static final String TOKEN_TYPE_SYNOnYM= "SYNONYM";

    private Stack synonymStack;
    private SynonymEngine synonymEngine;
    private AttributeSource.State current;
    private final CharTermAttribute bytesTermAttribute;
    private final PositionIncrementAttribute positionIncrementAttribute;

    /**
     * Construct a token stream filtering the given input.
     *
     * @param input
     */
    protected SynonymFilter(TokenStream input, SynonymEngine synonymEngine) {
        super(input);
        this.synOnymEngine= synonymEngine;
        synOnymStack= new Stack<>();

        this.bytesTermAttribute = addAttribute(CharTermAttribute.class);
        this.positiOnIncrementAttribute= addAttribute(PositionIncrementAttribute.class);
    }

    @Override
    public boolean incrementToken() throws IOException {
        if (!synonymStack.isEmpty()) {
            String syn = synonymStack.pop();
            restoreState(current);

//            bytesTermAttribute.setBytesRef(new BytesRef(syn.getBytes()));
//            bytesTermAttribute.resizeBuffer(0);
            bytesTermAttribute.append(syn);

            positionIncrementAttribute.setPositionIncrement(0);
            return true;
        }

        if (!input.incrementToken()) {
            return false;
        }

        if (addAliasesToStack()) {
            current = captureState();
        }

        return true;
    }

    private boolean addAliasesToStack() throws IOException {
        String[] synOnyms= synonymEngine.getSynonyms(bytesTermAttribute.toString());
        if (synOnyms== null) {
            return false;
        }
        for (String synonym : synonyms) {
            synonymStack.push(synonym);
        }
        return true;
    }
}
 
 
Analyzer,用于将tokenizer和filter串联起来:
 
public class SynonymAnalyzer extends Analyzer {
    @Override
    protected TokenStreamComponents createComponents(String fieldName) {
        StandardTokenizer source = new StandardTokenizer();
        return new TokenStreamComponents(source, new SynonymFilter(new StopFilter(new LowerCaseFilter(source),
                new CharArraySet(StopAnalyzer.ENGLISH_STOP_WORDS_SET, true)), new TestSynonymEngine()));
    }
}
 
 
我们定义一个简易的同义词匹配引擎:
 
public interface SynonymEngine {
    String[] getSynonyms(String s) throws IOException;
}

public class TestSynonymEngine implements SynonymEngine {

    public static final Map map = new HashMap<>();

    static {
        map.put("quick", new String[]{"fast", "speedy"});
    }

    @Override
    public String[] getSynonyms(String s) throws IOException {
        return map.get(s);
    }
}
 
对最终结果进行测试:
 
   
public static void main(String[] args) throws IOException {
        SynonymAnalyzer analyzer = new SynonymAnalyzer();
        TokenStream tokenStream = analyzer.tokenStream("contents", new StringReader("The quick brown fox"));
        tokenStream.reset();

        CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
        OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
        PositionIncrementAttribute positiOnIncrementAttribute=
                tokenStream.addAttribute(PositionIncrementAttribute.class);
        TypeAttribute typeAttribute = tokenStream.addAttribute(TypeAttribute.class);

        int position = 0;
        while (tokenStream.incrementToken()) {
            int positiOnIncrement= positionIncrementAttribute.getPositionIncrement();
            if (positionIncrement > 0) {
                position += positionIncrement;
                System.out.println();
                System.out.print(position + " : ");
            }

            System.out.printf("[%s : %d ->  %d : %s]", charTermAttribute.toString(), offsetAttribute.startOffset(), offsetAttribute.endOffset(),
                    typeAttribute.type());
        }
 
 
测试出的结果,可以看出位置1的谓词the已经被剔除,位置2处加入了较多的同义词,由于使用的append,所以同义词记在了一起。
 
2 : [quick : 4 ->  9 : ][quickspeedy : 4 ->  9 : ][quickfast : 4 ->  9 : ]
3 : [brown : 10 ->  15 : ]
4 : [fox : 16 ->  19 : ]
 
 
 
 
Solr同义词设置
 
Solr中的同义词使用的是 SynonymFilterFactory 来进行加载的,我们需要在定义schema时,对某个字段设置同义词时,可以使用:
 

        
            
            
            
            
            
            
        
        
            
            
            
            
            
            
            
        
    
 
 
需要配置对应的 synonyms 属性,指定 定义同义词的配置文件,设置是否忽略大小写等属性。
 
而在加载同义词时,对文件进行逐行读取(使用LineNumberReader),对于每一行的数据,先使用 => 作为分隔符,同义词在左右两边(左边作为input,右边作为output)都可以配置成多个,以逗号分隔,最后以笛卡尔积的形式将其放至map中。
 
String line = null;
    while ((line = in.readLine()) != null) {
      if (line.length() == 0 || line.charAt(0) == '#') {
        continue; // ignore empty lines and comments
      }

      // TODO: we could process this more efficiently.
      String sides[] = split(line, "=>");
      if (sides.length > 1) { // explicit mapping
        if (sides.length != 2) {
          throw new IllegalArgumentException("more than one explicit mapping specified on the same line");
        }
        String inputStrings[] = split(sides[0], ",");
        CharsRef[] inputs = new CharsRef[inputStrings.length];
        for (int i = 0; i    
  
 
所有的同义词加载完成后,会生成一个SynonymMap,该map就被用来在全文检索的过程中进行同义词替换。
 
在我们对某个单词进行查询时,可以查询到我们设置的字段query分析器结构,生成一个TokenizerChain对象,对应的Tokenizer为我们设置的分词器,filters为我们设置的过滤器链条,会根据过滤器链条Chain进行
 



 
 
 
通过input的方式设置同义词Filter,组成该链条结果。
 
@Override
  protected TokenStreamComponents createComponents(String fieldName) {
    Tokenizer tk = tokenizer.create();
    TokenStream ts = tk;
    for (TokenFilterFactory filter : filters) {
      ts = filter.create(ts);
    }
    return new TokenStreamComponents(tk, ts);
  }
 
 
而具体到每个FilterFactory,例如SynonymFilterFactory,都通过create方法来创建对应的Filter用于同义词过滤。
 
@Override
  public TokenStream create(TokenStream input) {
    // if the fst is null, it means there's actually no synonyms... just return the original stream
    // as there is nothing to do here.
    return map.fst == null ? input : new SynonymFilter(input, map, ignoreCase);
  }
 
 
创建一个SynonymFilter来进行最后真正的筛选,将同义词进行替换,整体的类结构图如下:
 


 
lucene内置的Token
 
lucene中除了内置的几个Tokenizer,在solr中的field analyzer以及index中也得到了应用,下面就对这几种filter进行测试,我们分析的文本为:Please email clark.ma@gmail.com by 09, re:aa-bb
 
 
StandardAnalyzer
1 : [please : 0 ->  6 : ]
2 : [email : 7 ->  12 : ]
3 : [clark.ma : 13 ->  21 : ]
4 : [gmail.com : 22 ->  31 : ]
6 : [09 : 35 ->  37 : ]
7 : [re:aa : 39 ->  44 : ]
8 : [bb : 45 ->  47 : ]
去除空格,标点符号,@;
 
ClassicAnalyzer
1 : [please : 0 ->  6 : ]
2 : [email : 7 ->  12 : ]
3 : [clark.ma@gmail.com : 13 ->  31 : ]
5 : [09 : 35 ->  37 : ]
6 : [re : 39 ->  41 : ]
7 : [aa : 42 ->  44 : ]
8 : [bb : 45 ->  47 : ]
能够识别互联网域名和email地址,
LetterTokenizer
1 : [Please : 0 ->  6 : word]
2 : [email : 7 ->  12 : word]
3 : [clark : 13 ->  18 : word]
4 : [ma : 19 ->  21 : word]
5 : [gmail : 22 ->  27 : word]
6 : [com : 28 ->  31 : word]
7 : [by : 32 ->  34 : word]
8 : [re : 39 ->  41 : word]
9 : [aa : 42 ->  44 : word]
10 : [bb : 45 ->  47 : word]
丢弃掉所有的非文本字符
KeywordTokenizer
1 : [Please email clark.ma@gmail.com by 09, re:aa-bb : 0 ->  47 : word]
 
将整个文本当做一个词元
LowerCaseTokenizer
1 : [please : 0 ->  6 : word]
2 : [email : 7 ->  12 : word]
3 : [clark : 13 ->  18 : word]
4 : [ma : 19 ->  21 : word]
5 : [gmail : 22 ->  27 : word]
6 : [com : 28 ->  31 : word]
7 : [by : 32 ->  34 : word]
8 : [re : 39 ->  41 : word]
9 : [aa : 42 ->  44 : word]
10 : [bb : 45 ->  47 : word]
对其所有非文本字符,过滤空格,标点符号,将所有的大写转换为小写
NGramTokenizer
可以定义最小minGramSize(default=1), 最大切割值maxGramSize(default=2),生成的词元较多。
假设minGramSize=2, maxGramSize=3,输入abcde,输出:ab abc abc bc bcd cd cde
读取字段并在给定范围内生成多个token
PathHierachyTokenizer
c:\my document\filea\fileB,new PathHierarchyTokenizer('\\', '/')
1 : [c: : 0 ->  2 : word][c:/my document : 0 ->  14 : word][c:/my document/filea : 0 ->  20 : word][c:/my document/filea/fileB : 0 ->  26 : word]
使用新的文件目录符去代替文本中的目录符
PatternTokenizer
需要两个参数,pattern正则表达式,group分组。
pattern=”[A-Z][A-Za-z]*” group=”0″
输入: “Hello. My name is Inigo Montoya. You killed my father. Prepare to die.”
输出: “Hello”, “My”, “Inigo”, “Montoya”, “You”, “Prepare”
进行正则表达式分组匹配
UAX29URLEmailTokenizer
1 : [Please : 0 ->  6 : ]
2 : [email : 7 ->  12 : ]
3 : [clark.ma@gmail.com : 13 ->  31 : ]
4 : [by : 32 ->  34 : ]
5 : [09 : 35 ->  37 : ]
6 : [re:aa : 39 ->  44 : ]
7 : [bb : 45 ->  47 : ]
去除空格和标点符号,但保留url和email连接
 
 
Lucene内置的TokenFilter
 
过滤器能够组成一个链表,每一个过滤器处理上一个过滤器处理过后的词元,所以过滤器的排序很有意义,第一个过滤器最好能处理大部分常规情况,最后一个过滤器是带有针对特殊性的。
 
 
ClassicFilter “I.B.M. cat’s can’t” ==> “I.B.M”, “cat”, “can’t” 经典过滤器,可以过滤无意义的标点,需要搭配ClassicTokenizer使用
ApostropheFilter
1 : [abc : 0 ->  3 : ]
2 : [I.B.M : 4 ->  9 : ]
3 : [cat : 10 ->  15 : ]
4 : [can : 16 ->  21 : ]
省略所有的上撇号
LowerCaseFilter
1 : [i.b.m : 0 ->  5 : ]
2 : [cat's : 6 ->  11 : ]
3 : [can't : 12 ->  17 : ]
转换成小写
TypeTokenFilter
如果email_type.txt设置为ALPHANUM,会保留该类型的所有分析结果,否则会被删除掉
给定一个文件并设置成白名单还是黑名单,只有符合条件的type才能被保留
TrimFilter   去掉空格
TruncateTokenFilter
1 : [I.B : 0 ->  5 : ]
2 : [cat : 6 ->  11 : ]
3 : [can : 12 ->  17 : ]
截取文本长度,左边为prefixLength=3
PatternCaptureGroupFilter 可配置属性pattern和preserve_original(是否保留原文) 从输入文本中保留能够匹配正则表达式的
PatternReplaceFilter    
StopFilter   创建一个自定义的停词词库列表,过滤器遇到停词就直接过滤掉
KeepWordFilter 与StopFilter的含义正好相反  
LengthFilter 设置一个最小值min和最大值max 为词元的长度设置在一个固定范围
WordDelimiterFilter

A:-符号 wi-fi 变成wi fi
B:驼峰写法 LoveSong 变成 love song 对应参数
C:字母-数字 xiaomi100 变成 xiaomi 100
D:–符号 like–me 变成 like me
E:尾部的’s符号 mother’s 变成 mother
F:-符号 wi-fi 变成 wifi 于规则A不同的是没有分成两个词元
G:-符号,数字之间 400-884586 变成 400884586
H:-符号 无论字母还是数字,都取消-符号 wi-fi-4 变成wifi4

 
其他参数
splitOnCaseChange=”1″ 默认1,关闭设为0 规则B
generateWordParts=”1″ 默认1 ,对应规则AB
generateNumberParts=”1″ 默认1 对应规则F
catenateWords=”1″ 默认0 对应规则A
splitOnNumerics=”1″ 默认1,关闭设0 规则C
stemEnglishPossessive 默认1,关闭设0 规则E
catenateNumbers=”1″ 默认0 对应规则G
catenateAll=”1″ 默认0 对应规则 H
preserveOriginal=”1″ 默认0 对词元不做任何修改 除非有其他参数改变了词元
protected=”protwords.txt” 指定这个单词列表的单词不被修改
通过分隔符分割单元
 
 
 
 
 
 
 
 
 
 

推荐阅读
  • ***byte(字节)根据长度转成kb(千字节)和mb(兆字节)**parambytes*return*publicstaticStringbytes2kb(longbytes){ ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • Java SE从入门到放弃(三)的逻辑运算符详解
    本文详细介绍了Java SE中的逻辑运算符,包括逻辑运算符的操作和运算结果,以及与运算符的不同之处。通过代码演示,展示了逻辑运算符的使用方法和注意事项。文章以Java SE从入门到放弃(三)为背景,对逻辑运算符进行了深入的解析。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 本文介绍了Java中Hashtable的clear()方法,该方法用于清除和移除指定Hashtable中的所有键。通过示例程序演示了clear()方法的使用。 ... [详细]
author-avatar
大爱走钢索的人_738
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有