zoukankan      html  css  js  c++  java
  • Lucene于搜索引擎技术(Analysis包详解)

    Analysis包分析

    算法和数据结构分析:

    由于Analysis包比较简单,不详述了!

    算法:基于机械分词 1-gram,2-gram,HMM(如果使用ICTCLAS接口的话)

    数据结构:部分源码用到了Set ,HashTable,HashMap

    认真理解Token

    Lucene中的Analysis包专门用于完成对于索引文件的分词.Lucene中的Token是一个非常重要的概念.

    看一下其源码实现:

    public final class Token {

     String termText;                       // the text of the term

     int startOffset;                          // start in source text

     int endOffset;                           // end in source text

     String type = "word";                       // lexical type

     private int positionIncrement = 1;

     public Token(String text, int start, int end)

     public Token(String text, int start, int end, String typ)

     public void setPositionIncrement(int positionIncrement)

     public int getPositionIncrement() { return positionIncrement; }

     public final String termText() { return termText; }

     public final int startOffset() { return startOffset; }

    public void setStartOffset(int givenStartOffset)

     public final int endOffset() { return endOffset; }

    public void setEndOffset(int givenEndOffset)

     public final String type() { return type; }

     public String toString()

     }

    下面编一段代码来看一下

    TestToken.java

    package org.apache.lucene.analysis.test;

    import org.apache.lucene.analysis.*;

    import org.apache.lucene.analysis.standard.StandardAnalyzer;

    import java.io.*;

    public class TestToken

    {

     public static void main(String[] args)

     {

       String string = new String("我爱天大,但我更爱中国");

    //Analyzer analyzer = new StandardAnalyzer();

    Analyzer analyzer = new TjuChineseAnalyzer();

    //Analyzer analyzer= new StopAnalyzer();

        TokenStream ts = analyzer.tokenStream("dummy",new StringReader(string));

        Token token;

        try

        {

          int n=0;

          while ( (token = ts.next()) != null)

          {

            System.out.println((n++)+"->"+token.toString());

          }

        }

        catch(IOException ioe)

        {

          ioe.printStackTrace();

        }

     

     }

    }注意看其结果如下所示

    0->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,0,1,<CJK>,1)

    1->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,1,2,<CJK>,1)

    2->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,2,3,<CJK>,1)

    3->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,3,4,<CJK>,1)

    4->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,5,6,<CJK>,1)

    5->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,6,7,<CJK>,1)

    6->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,7,8,<CJK>,1)

    7->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,8,9,<CJK>,1)

    8->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,9,10,<CJK>,1)

    9->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,10,11,<CJK>,1)

    注意:其中”,”StandardAnalyzer给过滤掉了,所以大家注意第4Token直接startOffset5开始.

    如果改用StopAnalyzer()

    0->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(我爱天大,0,4,word,1)

    1->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(但我更爱中国,5,11,word,1)

    改用TjuChineseAnalyzer(我写的,下文会讲到如何去写)

    0->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,3,4,word,1)

    1->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(天大,6,8,word,1)

    2->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,19,20,word,1)

    3->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,22,23,word,1)

    4->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(中国,25,27,word,1)

    讲明白了Token,咱们来看以下其他的东西

    一个TokenStream是用来走访Tokeniterator(迭代器)

    看一下其源代码:

    public abstract class TokenStream {

     public abstract Token next() throws IOException;

     public void close() throws IOException {}

    }

    一个Tokenizeris-a TokenStream(派生自TokenStream),其输入为Reader

    看一下其源码如下:

    public abstract class Tokenizer extends TokenStream {

     protected Reader input;

     protected Tokenizer() {}

     protected Tokenizer(Reader input) {

        this.input = input;

     }

     public void close() throws IOException {

        input.close();

     }

    }

    一个TokenFilter is–a TokenStream(派生自TokenStream),其义如名就是用来完成对TokenStream的过滤操作,譬如

    StopWords,将Token变为小写等。

    源码如下:

    public abstract class TokenFilter extends TokenStream {

     protected TokenStream input;

     protected TokenFilter() {}

     protected TokenFilter(TokenStream input) {

        this.input = input;

     }

     public void close() throws IOException {

        input.close();

     }

    }

    一个Analyzer就是一个TokenStream工厂

    看一下其源码就:

    public abstract class Analyzer { 

     public TokenStream tokenStream(String fieldName, Reader reader)

     {

            return tokenStream(reader);

     }

     public TokenStream tokenStream(Reader reader)

     {

            return tokenStream(null, reader);

     }

    }

    好,现在咱们来看一下LuceneAnalysis包下面的各个类文件都是用来干什么的。按照字典排序。

    Analysis包中的源码详解

    Analyzer.java 上文已经讲过。

    CharTokenizer.java 此类为简单一个抽象类,用来对基于字符的进行简单分词(tokenizer

    LetterTokenizer.java两个非字符之间的字符串定义为token(举例来说英文单词由空白隔开,那个两个空白之间的字符串即被定义为一个token。备注:对于绝大多数欧洲语言来说,这个类工作效能很好。当时对于不用空白符分割的亚洲语言,效能极差(譬如中日韩)。)

    LowerCaseFilter.java is-a TokenFilter用于将字母小写化

    LowerCaseTokenizer is-a Tokenizer功能上等价于LetterTokenizerLowerCaseFilter

    PerFieldAnalyzerWrapper是一个Analyzer,因为继承自Analyzer当不同的域(Field)需要不同的语言分析器(Analyzer)时,这个Analyzer就派上了用场。使用成员函数addAnalyzer可以增加一个非缺省的基于某个Fieldanalyzer。很少使用。

    PorterStemFilter.java使用词干抽取算法对每一个token流进行词干抽取。

    PorterStemmer.java 有名的P-stemming算法

    SimpleAnalyzer.java

    StopAnalyzer.java   具有过滤停用词的功能

    StopFilter.java     StopFilter为一个Filter,主要用于从token流中去除StopWords

    Token.java       上面已讲.

    TokenFilter.java   上面已经讲了

    Tokenizer.java     上面已经讲了

    TokenStream.java   上面已经讲了

    WhitespaceAnalyzer.java

    WhitespaceTokenizer.java 只是按照space区分Token.

     

    由于Luceneanalyisis包下的Standard包下的StandardAnalyzer()功能很强大,而且支持CJK分词,我们简要说一下.

    此包下的文件是有StandardTokenizer.jj经过javac命令生成的.由于是机器自动生成的代码,可能可读性很差,想了解的话好好看看那个StandardTokenizer.jj文件就会比较明了了.

    Lucene常用的Analyzer功能概述.

    WhitespaceAnalyzer:仅仅是去除空格,对字符没有lowcase,不支持中文

    SimpleAnalyzer:功能强于WhitespaceAnalyzer,将除去letter之外的符号全部过滤掉,并且将所有的字符lowcase,不支持中文

    StopAnalyzer:StopAnalyzer的功能超越了SimpleAnalyzer,在SimpleAnalyzer的基础上
       
    增加了去除StopWords的功能,不支持中文

    StandardAnalyzer:英文的处理能力同于StopAnalyzer.支持中文采用的方法为单字切分.

    ChineseAnalyzer:来自于Lucenesand box.性能类似于StandardAnalyzer,缺点是不支持中英文混和分词.

    CJKAnalyzer:chedong写的CJKAnalyzer的功能在英文处理上的功能和StandardAnalyzer相同
       
    但是在汉语的分词上,不能过滤掉标点符号,即使用二元切分

    TjuChineseAnalyzer:我写的,功能最为强大.TjuChineseAnlyzer的功能相当强大,在中文分词方面由于其调用的为ICTCLASjava接口.所以其在中文方面性能上同与ICTCLAS.其在英文分词上采用了LuceneStopAnalyzer,可以去除 stopWords,而且可以不区分大小写,过滤掉各类标点符号.

    各个Analyzer的功能已经比较介绍完毕了,现在咱们应该学写Analyzer,如何diy自己的analyzer??

    如何DIY一个Analyzer

    咱们写一个Analyzer,要求有一下功能

    (1)    可以处理中文和英文,对于中文实现的是单字切分,对于英文实现的是以空格切分.

    (2)    对于英文部分要进行小写化.

    (3)    具有过滤功能,可以人工设定StopWords列表.如果不是人工设定,系统会给出默认的StopWords列表.

    (4)    使用P-stemming算法对于英文部分进行词缀处理.

    代码如下:

    public final class DiyAnalyzer

        extends Analyzer

    {

     private Set stopWords;

     public static final String[] CHINESE_ENGLISH_STOP_WORDS =

          {

          "a", "an", "and", "are", "as", "at", "be", "but", "by",

          "for", "if", "in", "into", "is", "it",

          "no", "not", "of", "on", "or", "s", "such",

          "t", "that", "the", "their", "then", "there", "these",

          "they", "this", "to", "was", "will", "with",

          "", "我们"

     };

     public DiyAnalyzer()

     {

        this.stopWords=StopFilter.makeStopSet(CHINESE_ENGLISH_STOP_WORDS);

     }

     

     public DiyAnalyzer(String[] stopWordList)

     {

        this.stopWords=StopFilter.makeStopSet(stopWordList);

     }

     

     public TokenStream tokenStream(String fieldName, Reader reader)

     {

        TokenStream result = new StandardTokenizer(reader);

        result = new LowerCaseFilter(result);

     

        result = new StopFilter(result, stopWords);

        result = new PorterStemFilter(result);

        return result;

     }

     

     public static void main(String[] args)

     {

        //好像英文的结束符号标点.,StandardAnalyzer不能识别

        String string = new String("我爱中国,我爱天津大学!I love China!Tianjin is a City");

        Analyzer analyzer = new DiyAnalyzer();

        TokenStream ts = analyzer.tokenStream("dummy", new StringReader(string));

        Token token;

        try

        {

          while ( (token = ts.next()) != null)

          {

            System.out.println(token.toString());

          }

        }

        catch (IOException ioe)

        {

          ioe.printStackTrace();

        }

     }

    }

    可以看见其后的结果如下:

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,1,2,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,2,3,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,3,4,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,6,7,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,7,8,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,8,9,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,9,10,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,10,11,<CJK>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(i,12,13,<ALPHANUM>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(love,14,18,<ALPHANUM>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(china,19,24,<ALPHANUM>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(tianjin,25,32,<ALPHANUM>,1)

    Token's (termText,startOffset,endOffset,type,positionIncrement) is:(citi,39,43,<ALPHANUM>,1)

     

    到此为止这个简单的但是功能强大的分词器就写完了,下面咱们可以尝试写一个功能更强大的分词器.

    如何DIY一个功能更加强大Analyzer

    譬如你有词典,然后你根据正向最大匹配法或者逆向最大匹配法写了一个分词方法,却想在Lucene中应用,很简单

    你只要把他们包装成LuceneTokenStream就好了.下边我以调用中科院写的ICTCLAS接口为例,进行演示.你去中科院

    网站可以拿到此接口的free版本,谁叫你没钱呢,有钱,你就可以购买了.哈哈

    ,由于ICTCLAS进行分词之后,Java,中间会以两个空格隔开!too easy,我们直接使用继承Lucene

    WhiteSpaceTokenizer就好了.

    所以TjuChineseTokenizer 看起来像是这样.

    public class TjuChineseTokenizer extends WhitespaceTokenizer

    {

     public TjuChineseTokenizer(Reader readerInput)

     {

        super(readerInput);

     }

    }

    TjuChineseAnalyzer看起来象是这样

    public final class TjuChineseAnalyzer

        extends Analyzer

    {

     private Set stopWords;

     

     /** An array containing some common English words that are not usually useful

        for searching. */

     /*

         public static final String[] CHINESE_ENGLISH_STOP_WORDS =

          {

          "a", "an", "and", "are", "as", "at", "be", "but", "by",

          "for", "if", "in", "into", "is", "it",

          "no", "not", "of", "on", "or", "s", "such",

          "t", "that", "the", "their", "then", "there", "these",

          "they", "this", "to", "was", "will", "with",

          "", "我们"

         };

       */

     /** Builds an analyzer which removes words in ENGLISH_STOP_WORDS. */

     public TjuChineseAnalyzer()

     {

        stopWords = StopFilter.makeStopSet(StopWords.SMART_CHINESE_ENGLISH_STOP_WORDS);

     }

     

     /** Builds an analyzer which removes words in the provided array. */

     //提供独自的stopwords

     public TjuChineseAnalyzer(String[] stopWords)

     {

        this.stopWords = StopFilter.makeStopSet(stopWords);

     }

     

     /** Filters LowerCaseTokenizer with StopFilter. */

     public TokenStream tokenStream(String fieldName, Reader reader)

     {

        try

        {

          ICTCLAS splitWord = new ICTCLAS();

          String inputString = FileIO.readerToString(reader);

          //分词中间加入了空格

          String resultString = splitWord.paragraphProcess(inputString);

          System.out.println(resultString);

          TokenStream result = new TjuChineseTokenizer(new StringReader(resultString));

     

          result = new LowerCaseFilter(result);

          //使用stopWords进行过滤

         result = new StopFilter(result, stopWords);

          //使用p-stemming算法进行过滤

         result = new PorterStemFilter(result);

          return result;

     

        }

        catch (IOException e)

        {

          System.out.println("转换出错");

          return null;

        }

     }

     

     public static void main(String[] args)

     {

        String string = "我爱中国人民";

        Analyzer analyzer = new TjuChineseAnalyzer();

        TokenStream ts = analyzer.tokenStream("dummy", new StringReader(string));

        Token token;

        System.out.println("Tokens:");

        try

        {

          int n=0;

          while ( (token = ts.next()) != null)

          {

            System.out.println((n++)+"->"+token.toString());

          }

        }

        catch (IOException ioe)

        {

         ioe.printStackTrace();

        }

     }

    }对于此程序的输出接口可以看一下

    0->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(,3,4,word,1)

    1->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(中国,6,8,word,1)

    2->Token's (termText,startOffset,endOffset,type,positionIncrement) is:(人民,10,12,word,1)

     

    OK,经过这样一番讲解,你已经对LuceneAnalysis包认识的比较好了,当然如果你想更加了解,还是认真读读源码才好,

    呵呵,源码说明一切!

  • 相关阅读:
    BZOJ 2034 【2009国家集训队】 最大收益
    vijos P1780 【NOIP2012】 开车旅行
    BZOJ 2115 【WC2011】 Xor
    BZOJ 3631 【JLOI2014】 松鼠的新家
    BZOJ 4717 改装
    BZOJ 2957 楼房重建
    BZOJ 4034 【HAOI2015】 T2
    BZOJ 1834 【ZJOI2010】 network 网络扩容
    BZOJ 2440 【中山市选2011】 完全平方数
    BZOJ 2733 【HNOI2012】 永无乡
  • 原文地址:https://www.cnblogs.com/cy163/p/1215500.html
Copyright © 2011-2022 走看看