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该如何对输入的文档进行索引。
对于每个不同的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中的词,最后将剩下的词元转换为同义词;对于查询,先经过一个基本的分词器,然后转换为小写字母就可以了。
<fieldType name="nametext" class="solr.TextField"> <analyzer type="index"> <tokenizer class="solr.StandardTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.KeepWordFilterFactory" words="keepwords.txt"/> <filter class="solr.SynonymFilterFactory" synonyms="syns.txt"/> </analyzer> <analyzer type="query"> <tokenizer class="solr.StandardTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldType>
在Lucene实战一书中,详解了如何从头编写一个同义词Analyzer,通过改写termAttribute以及positionIncrementAttribute的方式来达到实现同义词的方式,不过由于书上的示例比较陈旧,而charTermAttribute不能达到修改同义词元的目的(只能进行append),因此替换最终的目的没有达到。
public class SynonymFilter extends TokenFilter { private static final String TOKEN_TYPE_SYNONYM = "SYNONYM"; private Stack<String> 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<String, String[]> 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 : <ALPHANUM>][quickspeedy : 4 -> 9 : <ALPHANUM>][quickfast : 4 -> 9 : <ALPHANUM>] 3 : [brown : 10 -> 15 : <ALPHANUM>] 4 : [fox : 16 -> 19 : <ALPHANUM>]
Solr同义词设置
Solr中的同义词使用的是 SynonymFilterFactory 来进行加载的,我们需要在定义schema时,对某个字段设置同义词时,可以使用:
<fieldtype name="textComplex" class="solr.TextField" positionIncrementGap="100"> <analyzer type="index"> <tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="complex" dicPath="/Users/mazhiqiang/develop/tools/solr-5.5.0/server/solr/product/conf/dic" /> <filter class="solr.StopFilterFactory" ignoreCase="false" words="stopwords.txt"/> <filter class="solr.WordDelimiterFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.NGramFilterFactory" minGramSize="1" maxGramSize="20"/> <filter class="solr.StandardFilterFactory"/> </analyzer> <analyzer type="query"> <tokenizer class="com.chenlb.mmseg4j.solr.MMSegTokenizerFactory" mode="complex" dicPath="/Users/mazhiqiang/develop/tools/solr-5.5.0/server/solr/product/conf/dic" /> <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/> <filter class="solr.StopFilterFactory" ignoreCase="false" words="stopwords.txt"/> <filter class="solr.WordDelimiterFilterFactory"/> <filter class="solr.LowerCaseFilterFactory"/> <!-- <filter class="solr.EdgeNGramFilterFactory" minGramSize="1" maxGramSize="20"/> --> <filter class="solr.RemoveDuplicatesTokenFilterFactory"/> </analyzer> </fieldtype>
需要配置对应的 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 < inputs.length; i++) { inputs[i] = analyze(unescape(inputStrings[i]).trim(), new CharsRefBuilder()); } String outputStrings[] = split(sides[1], ","); CharsRef[] outputs = new CharsRef[outputStrings.length]; for (int i = 0; i < outputs.length; i++) { outputs[i] = analyze(unescape(outputStrings[i]).trim(), new CharsRefBuilder()); } // these mappings are explicit and never preserve original for (int i = 0; i < inputs.length; i++) { for (int j = 0; j < outputs.length; j++) { add(inputs[i], outputs[j], false); } }
所有的同义词加载完成后,会生成一个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 : <ALPHANUM>]
2 : [email : 7 -> 12 : <ALPHANUM>]
3 : [clark.ma : 13 -> 21 : <ALPHANUM>]
4 : [gmail.com : 22 -> 31 : <ALPHANUM>]
6 : [09 : 35 -> 37 : <NUM>]
7 : [re:aa : 39 -> 44 : <ALPHANUM>]
8 : [bb : 45 -> 47 : <ALPHANUM>]
|
去除空格,标点符号,@;
|
ClassicAnalyzer |
1 : [please : 0 -> 6 : <ALPHANUM>]
2 : [email : 7 -> 12 : <ALPHANUM>]
3 : [clark.ma@gmail.com : 13 -> 31 : <EMAIL>]
5 : [09 : 35 -> 37 : <ALPHANUM>]
6 : [re : 39 -> 41 : <ALPHANUM>]
7 : [aa : 42 -> 44 : <ALPHANUM>]
8 : [bb : 45 -> 47 : <ALPHANUM>]
|
能够识别互联网域名和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 : <ALPHANUM>]
2 : [email : 7 -> 12 : <ALPHANUM>]
3 : [clark.ma@gmail.com : 13 -> 31 : <EMAIL>]
4 : [by : 32 -> 34 : <ALPHANUM>]
5 : [09 : 35 -> 37 : <NUM>]
6 : [re:aa : 39 -> 44 : <ALPHANUM>]
7 : [bb : 45 -> 47 : <ALPHANUM>]
|
去除空格和标点符号,但保留url和email连接 |
Lucene内置的TokenFilter
过滤器能够组成一个链表,每一个过滤器处理上一个过滤器处理过后的词元,所以过滤器的排序很有意义,第一个过滤器最好能处理大部分常规情况,最后一个过滤器是带有针对特殊性的。
ClassicFilter | “I.B.M. cat’s can’t” ==> “I.B.M”, “cat”, “can’t” | 经典过滤器,可以过滤无意义的标点,需要搭配ClassicTokenizer使用 |
ApostropheFilter |
1 : [abc : 0 -> 3 : <ALPHANUM>]
2 : [I.B.M : 4 -> 9 : <ALPHANUM>]
3 : [cat : 10 -> 15 : <ALPHANUM>]
4 : [can : 16 -> 21 : <ALPHANUM>]
|
省略所有的上撇号 |
LowerCaseFilter |
1 : [i.b.m : 0 -> 5 : <ALPHANUM>]
2 : [cat's : 6 -> 11 : <ALPHANUM>]
3 : [can't : 12 -> 17 : <ALPHANUM>]
|
转换成小写 |
TypeTokenFilter |
<filter class=”solr.TypeTokenFilterFactory” types=”email_type.txt” useWhitelist=”true”/>
如果email_type.txt设置为ALPHANUM,会保留该类型的所有分析结果,否则会被删除掉
|
给定一个文件并设置成白名单还是黑名单,只有符合条件的type才能被保留 |
TrimFilter | 去掉空格 | |
TruncateTokenFilter |
1 : [I.B : 0 -> 5 : <ALPHANUM>]
2 : [cat : 6 -> 11 : <ALPHANUM>]
3 : [can : 12 -> 17 : <ALPHANUM>]
|
截取文本长度,左边为prefixLength=3 |
PatternCaptureGroupFilter | 可配置属性pattern和preserve_original(是否保留原文) | 从输入文本中保留能够匹配正则表达式的 |
PatternReplaceFilter | ||
StopFilter | 创建一个自定义的停词词库列表,过滤器遇到停词就直接过滤掉 | |
KeepWordFilter | 与StopFilter的含义正好相反 | |
LengthFilter | 设置一个最小值min和最大值max | 为词元的长度设置在一个固定范围 |
WordDelimiterFilter |
A:-符号 wi-fi 变成wi fi 其他参数
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” 指定这个单词列表的单词不被修改
|
通过分隔符分割单元 |
相关推荐
而`TokenFilter`是Lucene中用于分词过滤的一个关键组件,它允许我们在分词过程中对原始词汇进行修改或筛选。本文将详细介绍如何使用`TokenFilter`进行再分词,并结合提供的源代码进行解析。 首先,理解Lucene的分词...
源码中的TokenStream、Tokenizer和TokenFilter等接口和类是Analyzer的核心组成部分,理解它们之间的关系和使用方法,可以帮助我们更好地控制文本分析过程。 在实际应用中,我们可能会遇到各种复杂的文本处理需求,...
本篇将聚焦于"Lucene5学习之自定义同义词分词器简单示例",通过这个主题,我们将深入探讨如何在Lucene5中自定义分词器,特别是实现同义词扩展,以提升搜索质量和用户体验。 首先,理解分词器(Analyzer)在Lucene中...
【Lucene 3.6 学习笔记】 Lucene 是一个高性能、全文本搜索库,广泛应用于各种搜索引擎的开发。本文将深入探讨Lucene 3.6版本中的关键概念、功能以及实现方法。 ### 第一章 Lucene 基础 #### 1.1 索引部分的核心...
3. Tokenizer 产生的 Token 会通过一系列的 TokenFilter 进行过滤和转换,以满足不同的分析需求,例如去除停用词、词形还原等。 4. 分析器的分析效果主要通过查看 TokenStream 中的内容来判断。每个 Analyzer 都有一...
**Lucene 基础学习笔记与源码分析** **一、Lucene 概述** Lucene 是一个高性能、全文本搜索库,由 Apache 软件基金会开发并维护。它是一个 Java 开发的开源项目,被广泛应用于各种搜索引擎的构建,支持多种编程...
5. **分词器和字符过滤器**:Lucene允许自定义分词规则,源码中可能会包含自定义Analyzer、Tokenizer和TokenFilter的示例,展示如何处理各种语言的文本。 6. **高亮显示**:为了使搜索结果更具可读性,源码会展示...
1. 分词器(Tokenizer):这是Lucene处理文本的第一步,它将原始文本分割成一系列的词语(Token)。源码中包含多种分词器,如StandardTokenizer,用于处理英文文本,以及CJKTokenizer,针对中文、日文、韩文等东亚...
Analyzer在1.4.3版本中包括了标准Analyzer,它使用了Tokenizer、TokenFilter和CharFilter等组件,负责词的切分、去除停用词、转换大小写等任务。开发者可以根据需求自定义Analyzer。 Lucene的索引构建流程主要包括...
分析器包括分词器(Tokenizer)、过滤器(TokenFilter)和字符过滤器(CharFilter)。 3. **查询解析** `QueryParser`类用于将用户输入的查询字符串转化为可执行的`Query`对象。它可以处理多种查询语法,如布尔...
在本文中,我们将深入探讨Lucene 3.0版本的主要特性、工作原理以及如何将其应用到实际项目中。 一、Lucene 3.0概述 Lucene 3.0是Lucene发展过程中的一个重要里程碑,它在稳定性、性能和功能上都有所提升。这一版本...
在压缩包文件“lucene-1.4.1”中,我们可以找到早期版本的Lucene源代码,这对于理解Lucene的历史发展和演进,以及学习如何从底层实现搜索功能非常有帮助。通过阅读和分析源代码,开发者可以更深入地了解Lucene的工作...
1. 分析器(Analyzer):分析器是Lucene.NET中处理文本的核心组件,负责将原始输入文本转换为一系列的Token(分词结果)。对于中文,`Lucene.Net.Analysis.Cn.Standard.CJKStandardAnalyzer`是常用的选择,它包含...
1. 分词器(Tokenizer):分词器是Lucene处理文本的第一步,负责将输入的字符串分解为一系列的词元(Token)。Lucene.Net包含多种预定义的分词器,如StandardAnalyzer用于英文,而ChineseAnalyzer适用于中文。 2. ...
在Lucene中,分词器(Tokenizer)负责将输入的文本分割成一个个独立的词语,然后通过词过滤器(TokenFilter)进行进一步的处理,如去除停用词、词形还原等。 在处理中文文本时,Lucene需要特殊处理,因为中文的分词...
此外,还有一些辅助类,如TokenFilter和Tokenizer,它们在分词过程中起到过滤和切分的作用。 “META-INF”目录则包含了Lucene的相关元数据,如MANIFEST.MF文件,它记录了jar包的基本信息和依赖关系,这对于构建和...
例如,`Analyzer`可能有一个抽象方法`TokenStream`,`TokenStream`又有`Tokenizer`和`TokenFilter`作为子类,它们共同完成词元化任务。`Document`和`Field`是构成索引的基本元素,而`IndexWriter`通过操作`...
1. 分词器(Tokenizer):Lucene.NET使用分词器将输入的文本分割成可搜索的词元。例如,英文的停用词(如“the”、“is”)会被过滤,而名词、动词等保留,以便进行更精准的匹配。 2. 字符过滤器(CharacterFilter...
这个过程中涉及的关键技术包括分词器(Tokenizer)、词元过滤器(TokenFilter)和分析器(Analyzer),它们用于处理文本并生成可搜索的表示形式。 在构建基于Lucene的搜索引擎时,我们首先需要设置一个分析器来处理...
此外,还可以实现自定义TokenFilter和Tokenizer,对分词结果进行进一步处理,如去除停用词、词性标注等。 接下来是字段配置。在Lucene中,每个文档由多个字段组成,每个字段可以设置不同的属性,如是否存储原始值、...