Lucene 实战教程第八章说说 TokenStream 和 TokenFilter

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

: » Lucene 实战教程第八章说说 TokenStream 和 TokenFilter

原创文章,作者:dweifng,如若转载,请注明出处:https://blog.ytso.com/tech/java/251964.html

(0)
上一篇 2022年5月3日 20:29
下一篇 2022年5月3日 20:33

相关推荐

发表回复

登录后才能评论