详解 org.apache.lucene.analysis.Analyzer 使用教程

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 使用教程

: » 详解 org.apache.lucene.analysis.Analyzer 使用教程

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

(0)
上一篇 2022年5月3日 20:09
下一篇 2022年5月3日 20:13

相关推荐

发表回复

登录后才能评论