当你深入骨髓的去了解一项技术,你才能获得快感!上一章,我们说过 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