Analyzer 单词的意思就是分析,它是一个抽象类,在 Lucene 的 org.apache.lucene.analysis 包中。lucene-analyzers-common 包中提供了非常多的分析器。比如:StandardAnalyzer 标准分词器;WhitespaceAnalyzer 根据空格拆分语汇单元;SimpleAnalyzer 根据非字母拆分文本,并将其转换为小写形式;StopAnalyzer 根据非字母拆分文本,然后小写化,再移除停用词;KeywordAnalyzer 将整个文本作为一个单一语汇单元处理;StandardAnalyzer 根据Unicode文本分割算法,具体算法参考Unicode Standard Annex #29,然后将文本转化为小写,并移除英文停用词;SmartChineseAnalyzer;CJKAnalyzer 等。本文将简单的介绍一下 Analyzer。
Analyzer 中有一个重要的方法就是 tokenStream()。tokenStream() 有两个重载方法,一个是从 reader 中获取文本,还有一个从字符串中获取文本,最终都以 TokenStream 作为输出结果。
TokenStream 是分析处理组件中的一种中间数据格式。
在所有的过滤器中,TokenStream 同时充当着输入和输出格式。Tokenizer 和 TokenFilter 继承自 TokenStream,Tokenizer 是一个 TokenStream,其输入源是一个 Reader;TokenFilter 也是一个 TokenStream,其输入源是另一个 TokenStream。而 TokenStream 简单点说就是生成器的输出结果,TokenStream 也是一个分词后的 Token 结果组成的流,通过流能够不断的得到下一个 Token。
@Test public void testTokenStream() throws IOException { Analyzer analyzer = new WhitespaceAnalyzer(); String inputText = "This is a test text for token!"; TokenStream tokenStream = analyzer.tokenStream("text", new StringReader(inputText)); //保存token字符串 CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class); //在调用incrementToken()开始消费token之前需要重置stream到一个干净的状态 tokenStream.reset(); while (tokenStream.incrementToken()) { //打印分词结果 System.out.print("[" + charTermAttribute + "]"); } }
Attribute
Analyzer 中还有非常多的 Attribute,这也是一个非常重要的组件。
调用 tokenStream() 方法之后,我们可以通过为之添加多个 Attribute,从而可以了解到分词之后详细的词元信息,比如 CharTermAttribute 用于保存词元的内容,TypeAttribute 用于保存词元的类型。
在 Lucene 中提供了几种类型的 Attribute,每种类型的 Attribute 提供一个不同的方面或者 token 的元数据,罗列如下:
名称 | 作用 |
---|---|
CharTermAttribute | 表示token本身的内容 |
PositionIncrementAttribute | 表示当前token相对于前一个token的相对位置,也就是相隔的词语数量(例如“text for attribute”,text和attribute之间的getPositionIncrement为2),如果两者之间没有停用词,那么该值被置为默认值1 |
OffsetAttribute | 表示token的首字母和尾字母在原文本中的位置 |
TypeAttribute | 表示token的词汇类型信息,默认值为word,其它值有<ALPHANUM> <APOSTROPHE> <ACRONYM> <COMPANY> <EMAIL> <HOST> <NUM> <CJ> <ACRONYM_DEP> |
FlagsAttribute | 与TypeAttribute类似,假设你需要给token添加额外的信息,而且希望该信息可以通过分析链,那么就可以通过flags去传递 |
PayloadAttribute | 在每个索引位置都存储了payload(关键信息),当使用基于Payload的查询时,该信息在评分中非常有用 |
@Test public void testAttribute() throws IOException { Analyzer analyzer = new StandardAnalyzer(); String input = "This is a test text for attribute! Just add-some word."; TokenStream tokenStream = analyzer.tokenStream("text", new StringReader(input)); CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class); PositionIncrementAttribute positionIncrementAttribute = tokenStream.addAttribute(PositionIncrementAttribute.class); OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class); TypeAttribute typeAttribute = tokenStream.addAttribute(TypeAttribute.class); PayloadAttribute payloadAttribute = tokenStream.addAttribute(PayloadAttribute.class); payloadAttribute.setPayload(new BytesRef("Just")); tokenStream.reset(); while (tokenStream.incrementToken()) { System.out.print("[" + charTermAttribute + " increment:" + positionIncrementAttribute.getPositionIncrement() + " start:" + offsetAttribute .startOffset() + " end:" + offsetAttribute .endOffset() + " type:" + typeAttribute.type() + " payload:" + payloadAttribute.getPayload() + "]/n"); } tokenStream.end(); tokenStream.close(); }
调用 incrementToken() 结束迭代之后,调用 end() 和 close() 方法,其中 end() 可以唤醒当前 TokenStream 的处理器去做一些收尾工作,close() 可以关闭 TokenStream 和 Analyzer 去释放在分析过程中使用的资源。
TokenFilter
TokenFilter 主要用于 TokenStream 的过滤操作,用来处理 Tokenizer 或者上一个 TokenFilter 处理后的结果,如果是对现有分词器进行扩展或修改,推荐使用自定义 TokenFilter 方式。自定义 TokenFilter 需要实现 incrementToken() 抽象函数,并且该方法需要声明为 final 的,在此函数中对过滤 Term 的 CharTermAttribute 和 PositionIncrementAttribute 等属性进行操作,就能实现过滤功能。
下面看一个简单的词扩展过滤器 CourtesyTitleFilter。
public class TestTokenFilter { @Test public void test() throws IOException { String text = "Hi, Dr Wang, Mr Liu asks if you stay with Mrs Liu yesterday!"; Analyzer analyzer = new WhitespaceAnalyzer(); CourtesyTitleFilter filter = new CourtesyTitleFilter(analyzer.tokenStream("text", text)); CharTermAttribute charTermAttribute = filter.addAttribute(CharTermAttribute.class); filter.reset(); while (filter.incrementToken()) { System.out.print(charTermAttribute + " "); } } } /** * 自定义词扩展过滤器 */ class CourtesyTitleFilter extends TokenFilter { Map<String, String> courtesyTitleMap = new HashMap<>(); private CharTermAttribute termAttribute; /** * Construct a token stream filtering the given input. * * @param input */ protected CourtesyTitleFilter(TokenStream input) { super(input); termAttribute = addAttribute(CharTermAttribute.class); courtesyTitleMap.put("Dr", "doctor"); courtesyTitleMap.put("Mr", "mister"); courtesyTitleMap.put("Mrs", "miss"); } @Override public final boolean incrementToken() throws IOException { if (!input.incrementToken()) { return false; } String small = termAttribute.toString(); if (courtesyTitleMap.containsKey(small)) { termAttribute.setEmpty().append(courtesyTitleMap.get(small)); } return true; } }
说了这么多,Analyzer 和 Tokenizer、以及 Tokenfilter 之间分别有什么区别呢?
Analyzer 主要包含分词器跟过滤器,他的功能就是:将分词器跟分析器进行合理的组合,使之产生对文本分词和过滤效果。因此,分析器使用分词和过滤器构成一个管道,文本在“滤过”这个管道之后,就成为可以进入索引的最小单位。
Tokenizer 主要用于对文本资源进行切分,将文本规则切分为一个个可以进入索引的最小单元。
Tokenfilter 主要对分词器切分的最小单位进入索引进行预处理,如:大写转小写,复数转单数,也可以复杂(根据语义改写拼写错误的单词)。
至于为什么这么做呢?这其中涉及到一些架构设计方面,设计模式方面的知识。大家结合起来看,就看的更明白一些。
: » 详解 org.apache.lucene.analysis.Analyzer 使用教程
原创文章,作者:bd101bd101,如若转载,请注明出处:https://blog.ytso.com/tech/java/251955.html