Lucene-Analyzer

Lucene文本解析器实现 把一段文本信息拆分成多个分词,我们都知道搜索引擎是通过分词检索的,文本解析器的好坏直接决定了搜索的精度和搜索的速度。

1.简单的Demo

    private static final String[] examples = { "The quick brown 1234 fox jumped over the lazy dog!","XY&Z 15.6 Corporation - [email protected]", "北京市北京大学" };
    private static final Analyzer[] ANALYZERS = new Analyzer[] {            new WhitespaceAnalyzer(), new SimpleAnalyzer(), new StopAnalyzer(), new StandardAnalyzer(), new CJKAnalyzer(), new SmartChineseAnalyzer() };
              //空格符拆分             非字母拆分             非字母拆分去掉停词       Unicode文本分割        日韩文分割               简体中文分割    @Test
    public void testAnalyzer() throws IOException {
        for (int i = 0; i < ANALYZERS.length; i++) {
            String simpleName = ANALYZERS[i].getClass().getSimpleName();
            for (int j = 0; j < examples.length; j++) {
                //TokenStream是分析处理组件中的一种中间数据格式,它从一个reader中获取文本, 分词器Tokenizer和过滤器TokenFilter继承自TokenStream
                TokenStream contents = ANALYZERS[i].tokenStream("contents", new StringReader(examples[j]));
                //添加多个Attribute,从而可以了解到分词之后详细的词元信息  ,OffsetAttribute 表示token的首字母和尾字母在原文本中的位置
                OffsetAttribute offsetAttribute = contents.addAttribute(OffsetAttribute.class);
                TypeAttribute typeAttribute = contents.addAttribute(TypeAttribute.class); //TypeAttribute 表示token的词汇类型信息,默认值为word
                contents.reset();
                System.out.println("  " + simpleName + " analyzing : " + examples[j]);
                while (contents.incrementToken()) {
                    String s1 = offsetAttribute.toString();
                    int i1 = offsetAttribute.startOffset();// 起始偏移量
                    int i2 = offsetAttribute.endOffset(); // 结束偏移量
                    System.out.println("    " + s1 + "[" + i1 + "," + i2 + ":" + typeAttribute.type() + "]" + "  ");
                }
                contents.end();
                contents.close(); //调用incrementToken()结束迭代之后,调用end()和close()方法,其中end()可以唤醒当前TokenStream的处理器去做一些收尾工作,close()可以关闭TokenStream和Analyzer去释放在分析过程中使用的资源。
                System.out.println();
            }
        }
    }
}

2. 了解tokenStream的Attribute

tokenStream()方法之后,添加多个Attribute,可以了解到分词之后详细的词元信息,比如CharTermAttribute用于保存词元的内容,TypeAttribute用于保存词元的类型。

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();
    }

3.Lucene 的分词器Tokenizer和过滤器TokenFilter

一个分析器由一个分词器和多个过滤器组成,分词器接受reader数据转换成 TokenStream,TokenFilter主要用于TokenStream的过滤操作,用来处理Tokenizer或者上一个TokenFilter处理后的结果,如果是对现有分词器进行扩展或修改

自定义TokenFilter需要实现incrementToken()抽象函数,

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;
    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;
    }
}

输出结果如下
   Hi, doctor Wang, mister Liu asks if you stay with miss Liu yesterday!

4.自定义Analyzer实现扩展停用词

class StopAnalyzerExtend extends Analyzer {
    private CharArraySet stopWordSet;//停止词词典

    public CharArraySet getStopWordSet() {
        return this.stopWordSet;
    }

    public void setStopWordSet(CharArraySet stopWordSet) {
        this.stopWordSet = stopWordSet;
    }

    public StopAnalyzerExtend() {
        super();
        setStopWordSet(StopAnalyzer.ENGLISH_STOP_WORDS_SET);
    }

    /**
     * @param stops 需要扩展的停止词
     */
    public StopAnalyzerExtend(List<String> stops) {
        this();
        /**如果直接为stopWordSet赋值的话,会报如下异常,这是因为在StopAnalyzer中有ENGLISH_STOP_WORDS_SET = CharArraySet.unmodifiableSet(stopSet);
         * ENGLISH_STOP_WORDS_SET 被设置为不可更改的set集合
         */
        //stopWordSet = getStopWordSet();
        stopWordSet = CharArraySet.copy(getStopWordSet());
        stopWordSet.addAll(StopFilter.makeStopSet(stops));
    }

    @Override
    protected TokenStreamComponents createComponents(String fieldName) {
        Tokenizer source = new LowerCaseTokenizer();
        return new TokenStreamComponents(source, new StopFilter(source, stopWordSet));
    }

    public static void main(String[] args) throws IOException {
        ArrayList<String> strings = new ArrayList<String>() {{
            add("小鬼子");
            add("美国佬");
        }};

        Analyzer analyzer = new StopAnalyzerExtend(strings);
        String content = "小鬼子 and 美国佬 are playing together!";
        TokenStream tokenStream = analyzer.tokenStream("myfield", content);
        tokenStream.reset();

        CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
        while (tokenStream.incrementToken()) {
            // 已经过滤掉自定义停用词
            // 输出:playing   together
            System.out.println(charTermAttribute.toString());
        }
        tokenStream.end();
        tokenStream.close();
    }
}

5.自定义Analyzer实现字长过滤

class LongFilterAnalyzer extends Analyzer {
    private int len;

    public int getLen() {
        return this.len;
    }

    public void setLen(int len) {
        this.len = len;
    }

    public LongFilterAnalyzer() {
        super();
    }

    public LongFilterAnalyzer(int len) {
        super();
        setLen(len);
    }

    @Override
    protected TokenStreamComponents createComponents(String fieldName) {
        final Tokenizer source = new WhitespaceTokenizer();
        //过滤掉长度<len,并且>20的token
        TokenStream tokenStream = new LengthFilter(source, len, 20);
        return new TokenStreamComponents(source, tokenStream);
    }

    public static void main(String[] args) {
        //把长度小于2的过滤掉,开区间
        Analyzer analyzer = new LongFilterAnalyzer(2);
        String words = "I am a java coder! Testingtestingtesting!";
        TokenStream stream = analyzer.tokenStream("myfield", words);
        try {
            stream.reset();
            CharTermAttribute offsetAtt = stream.addAttribute(CharTermAttribute.class);
            while (stream.incrementToken()) {
                System.out.println(offsetAtt.toString());
            }
            stream.end();
            stream.close();
        } catch (IOException e) {
        }
    }
}长度小于两个字符的文本都被过滤掉了。

6.PerFieldAnalyzerWrapper 处理不同的Field使用不同的Analyzer 。PerFieldAnalyzerWrapper可以像其它的Analyzer一样使用,包括索引和查询分析

    @Test
    public void testPerFieldAnalyzerWrapper() throws IOException, ParseException {
        Map<String, Analyzer> fields = new HashMap<>();
        fields.put("partnum", new KeywordAnalyzer());
        // 对于其他的域,默认使用SimpleAnalyzer分析器,对于指定的域partnum使用KeywordAnalyzer
        PerFieldAnalyzerWrapper perFieldAnalyzerWrapper = new PerFieldAnalyzerWrapper(new SimpleAnalyzer(), fields);

        Directory directory = new RAMDirectory();
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(perFieldAnalyzerWrapper);
        IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
        Document document = new Document();
        FieldType fieldType = new FieldType();
        fieldType.setStored(true);
        fieldType.setIndexOptions(IndexOptions.DOCS_AND_FREQS);
        document.add(new Field("partnum", "Q36", fieldType));
        document.add(new Field("description", "Illidium Space Modulator", fieldType));
        indexWriter.addDocument(document);
        indexWriter.close();

        IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(directory));
        // 直接使用TermQuery是可以检索到的
        TopDocs search = indexSearcher.search(new TermQuery(new Term("partnum", "Q36")), 10);
        Assert.assertEquals(1, search.totalHits);
        // 如果使用QueryParser,那么必须要使用PerFieldAnalyzerWrapper,否则如下所示,是检索不到的
        Query description = new QueryParser("description", new SimpleAnalyzer()).parse("partnum:Q36 AND SPACE");
        search = indexSearcher.search(description, 10);
        Assert.assertEquals(0, search.totalHits);
        System.out.println("SimpleAnalyzer :" + description.toString());// +partnum:q
                                                                        // +description:space,原因是SimpleAnalyzer会剥离非字母字符并将字母小写化
        // 使用PerFieldAnalyzerWrapper可以检索到
        // partnum:Q36 AND SPACE表示在partnum中出现Q36,在description中出现SPACE
        description = new QueryParser("description", perFieldAnalyzerWrapper).parse("partnum:Q36 AND SPACE");
        search = indexSearcher.search(description, 10);
        Assert.assertEquals(1, search.totalHits);
        System.out.println("(SimpleAnalyzer,KeywordAnalyzer) :" + description.toString());// +partnum:Q36 +description:space
    }

参考 : http://www.codepub.cn/2016/05/23/Lucene-6-0-in-action-4-The-text-analyzer/

时间: 2024-10-04 11:14:58

Lucene-Analyzer的相关文章

为推文优化的Lucene Analyzer类

<strong><span style="font-size:18px;">/*** * @author YangXin * @info 使用Doublemetaphone函数对Twitter优化. * Doublemetaphone函数能够为发音类似的单词创建同样的键 * */ package unitTwelve; import java.io.IOException; import org.apache.commons.codec.language.Dou

Lucene.Net 站内搜索

Lucene.Net 站内搜索 一  全文检索: like查询是全表扫描(为性能杀手)Lucene.Net搜索引擎,开源,而sql搜索引擎是收费的Lucene.Net只是一个全文检索开发包(只是帮我们存数据取数据,并没有界面,可以看作一个数据库,只能对文本信息进行检索)Lucene.Net原理:把文本切词保存,然后根据词汇表的页来找到文章 二  分词算法: //一元分词算法(引用Lucene.Net.dll)  一元分词算法 //二元分词算法(CJK:China Japan Korean 需要再

lucene+盘古分词

一般的网站都会有都会有搜索的功能,一般实现搜索主要有三种方案 第一种是最差的,也是最不推荐的,使用数据库的模糊查询例如select * form table where 字段 like XXX,这种查询的缺点很明显: (1)       无法查找几个关键词不连在一起的情况 (2)       全表扫描 效率低下 第二种:使用SqlServer的全文本检索功能 举例:select * form table where msg = ‘江苏南京’ 这是就可以写成select * form table

使用Lucene.Net实现全文检索

目录 一 Lucene.Net概述 二 分词 三 索引 四 搜索 五 实践中的问题 一 Lucene.Net概述 Lucene.Net是一个C#开发的开源全文索引库,其源码包括“核心”与“外围”两部分.外围部分实现辅助功能,而核心部分包括: Lucene.Net.Index 提供索引管理,词组排序. Lucene.Net.Search 提供查询相关功能. Lucene.Net.Store 支持数据存储管理,主要包括I/O操作. Lucene.Net.Util 公共类. Lucene.Net.Do

Lucene.net站内搜索2—Lucene.Net简介和分词

Lucene.Net简介 Lucene.Net是由Java版本的Lucene(卢思银)移植过来的,所有的类.方法都几乎和Lucene一模一样,因此使用时参考Lucene 即可.Lucene.Net只是一个全文检索开发包(就像ADO.Net和管理系统的关系),不是一个成型的搜索引擎,它的功能就是:把数据扔给Lucene.Net ,查询数据的时候从Lucene.Net 查询数据,可以看做是提供了全文检索功能的一个数据库.SQLServer中和Lucene.Net各存一份,目的不一样.Lucene.N

Lucene 4.4.0中常用的几个分词器

作者:ceclar123 推荐:刘超觉先 package bond.lucene.analyzer; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.core.WhitespaceAnalyzer; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.apache.lucene

全文检索之lucene的优化篇--分词器

在创建索引库的基础上,加上中文分词器的,更好的支持中文的查询.引入jar包je-analysis-1.5.3.jar,极易分词.还是先看目录. 建立一个分词器的包,analyzer,准备一个AnalyzerTest的类.里面的代码如下,主要写了一个testAnalyzer的方法,测试多种分词器对于中文和英文的分词;为了可以看到效果,所以写了个analyze()的方法,将分词器和text文本内容传入,并将分词的效果显示出来. package com.lucene.analyzer; import

全文检索-Lucene.net

Lucene.net是Lucene的.net移植版本,在较早之前是比较受欢迎的一个开源的全文检索引擎开发包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎. 例子的组件版本 Lucene.Net:3.0.3.0 盘古分词:2.4.0.0 分词例子 分词是核心算法,将完整的句子分词成若干个词或字:同时它只能处理文本信息,非文本信息只能转换成为文本信息,无法转换的只能放弃. 所有供全文搜索的要先写入索引库,索引库可以看成存放数据的数据库 搜索对象建立的时候

关于Tokenizer与TokenFilter的区别

TokenStream是一个能在被调用后产生语汇单元流的类,但是 TokenStream 类有两个不同的类型:Tokenizer 类和 TokenFilter 类.这两个类都是从抽象类TokenStream类继承而来. Tokenizer 对象通过Java.io.Reader 对象读取字符创建语汇单元,而TokenFilter 类则负责处理输入的语汇单元,然后通过新增.删除或者修改属性的方式来产生新的语汇单元. 当分词器从tokenStream 方法或者 reusableTokenStream

IK分词器 整合solr4.7 含同义词、切分词、停止词

IK分词器如果配置成 <fieldType name="text_ik" class="solr.TextField"> <analyzer type="index" isMaxWordLength="false" class="org.wltea.analyzer.lucene.IKAnalyzer"/> <analyzer type="query" is