当你深入骨髓的去了解一项技术,你才能获得快感!上一章,我们说过 Analyzer 分词器的主要是用来构建 TokenStreams,那么我们今天就一起通过本文来看看 Analyzer 的内部构造,TokenStream 和 TokenFilter。
TokenStream
TokenStream 是分析处理组件中的一种中间数据格式,它从一个 reader 中获取文本,并以 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 + "]"); } }
TokenStream 的使用总结起来可以分为 3 步。获得 TokenStream;重置 tokenStream.reset();获得 token,tokenStream.incrementToken()。
TokenAttribute
上面的代码中,我们使用了一个 CharTermAttribute。它是在调用 tokenStream() 方法之后获得的。我们可以通过 tokenStream.addAttribute 方法添加多个 Attribute。从而可以了解到分词之后详细的词元信息,比如 CharTermAttribute 用于保存词元的内容,TypeAttribute 用于保存词元的类型。
在 Lucene 中提供了几种类型的 Attribute,每种类型的 Attribute 提供一个不同的方面或者 token 的元数据。常用的 Attribute 如下:
名称 | 作用 |
---|---|
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的查询时,该信息在评分中非常有用 |
Attribute 用法如下:
@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 等属性进行操作,就能实现过滤功能。
一个简单的词扩展过滤器如下:
import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenFilter; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.core.WhitespaceAnalyzer; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.junit.Test; import java.io.IOException; import java.util.HashMap; import java.util.Map; 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; } }
通过 TokenStream、Tokenizer、TokenFilter、Analyzer 你可以看到一切简单而又紧密的结合在一起。其实看懂了就不觉得难了。
: » Lucene 实战教程第八章说说 TokenStream 和 TokenFilter
原创文章,作者:dweifng,如若转载,请注明出处:https://blog.ytso.com/tech/java/251964.html