Lucene Analyzer(分词器)

分词器能以某种规则对关键字进行分词,将分好的词放到目录中,以作为检索到的条件,在创建索引时会使用到分词器,在搜索时也将用到分词器,这两个地方要使用同一个分词器,否则可能找不到结果.分词器一般的工作流程:

  1. 切分关键词
  2. 去除停用词
  3. 对于英文单词,把所有字母转为小写

注:有的分词器对英文支持的非常好,还能对英文单词进行时态还原.

停用词的概念

有 些词在文本中出现的频率非常高,但是对文本所携带的信息基本不产生影响,例如英文的“a、an、the、of”,或中文的“的、了、着”,以及各种标点符 号等,这样的词称为停用词(stop word).文本经过分词之后,停用词通常被过滤掉,不会被进行索引.在检索的时候,用户的查询中如果含有停用词,检索系统也会将其过滤掉(因为用户输入 的查询字符串也要进行分词处理).排除停用词可以加快建立索引的速度,减小索引库文件的大小

常用的中文分词器

中文的分词比较复杂,因为不是一个字就是一个词,而且一个词在另外一个地方就可能不是一个词,如在"帽子和服装"中,"和服"就不是一个词.对于中文分词,通常有三种方式:

  • 单字分词
  • 二分法分词
  • 词典分词

单字分词:就是按照中文一个字一个字地进行分词.如:"我们是中国人",效果:"我"、"们"、"是"、"中"、"国"、"人".(StandardAnalyzer就是这样)

二分法分词:按两个字进行切分.如:"我们是中国人",效果:"我们"、"们是"、"是中"、"中国"、"国人".(CJKAnalyzer就是这样)

词 库分词:按某种算法构造词,然后去匹配已建好的词库集合,如果匹配到就切分出来成为词语.通常词库分词被认为是最理想的中文分词算法.如:"我们是中国 人",效果为:"我们"、"中国人".(使用极易分词的MMAnalyzer.可以使用极易分词,或者是庖丁分词分词器、IKAnalyzer等).

测试分词器

/* *
  *   使用指定的分词器对指定的文本进行分词,并打印结果
  *   @param   analyzer
  *   @param   text
  *   @throws   Exception
  */
private  void  testAnalyzer(Analyzer analyzer, String text) throws  Exception {
    System. out. println(" 当前使用的分词器: "  +  analyzer. getClass());
    TokenStream tokenStream =  analyzer. tokenStream(" content " new  StringReader(text));
    tokenStream. addAttribute(TermAttribute. class );
    while  (tokenStream. incrementToken()) {
        TermAttribute termAttribute =  tokenStream. getAttribute(TermAttribute. class );
        System. out. println(termAttribute. term());
    }
}
 


Highlighter(高亮器)

在搜索结果中经常可以看到,自己搜索的关键字加上了一些效果,以突出显示,Lucene自然提供了这样的设置,我们需要用到Highlighter这个类,用之前需要配置好:

/* *
  *   Formatter:设置高亮器的格式,无参构造函数表示使用<b>标签
  *   Scorer:Highlighter需要知道哪些关键词是需要高亮的,需要需要查询条件
  */
Formatter formatter =  new  SimpleHTMLFormatter();
Scorer scorer =  new  QueryScorer(query);
Highlighter highlighter =  new  Highlighter(formatter,scorer);
// 除了上面两个以外,还需要生成一段摘要,以便搜索的时候显示,指定摘要的大小为20个字符
Fragmenter fragmenter =  new  SimpleFragmenter(20 );
highlighter. setTextFragmenter(fragmenter);

只需要配置这三样就可以了,接着我们要在每一次获取Field对象数据的时候做一些工作,因为高亮器实际上就是在内容中加了一点样式,我们当然要指定在哪里加:

/* *
  *   在循环获取每一条记录的时候,让高亮器找到需要高亮的关键词,
  *   要提供相应的分词器和告诉它高亮哪一个Field中的内容
  *   一次只能高亮一个Field,如需要高亮多个Field,需要写多次
  *   就像这样:
     String   text1   =   highlighter.getBestFragment(
          Configuration.analyzer,   "title",  
          doc.get("title"));
  */
String text =  highlighter. getBestFragment(Configuration. analyzer, " content " , doc. get(" content " ));
if (text ! =  null ){
    doc. getField(" content " ). setValue(text);
}

一定要放在循环里面,highlighter . getBestFragment () 方法本身没有副作用,也就是不改变原有的值,返回一个改变后的结果,所以要手动地改变Field中原有的值,并且if的条件最好不要省略,有时候可以会有 这样一种情况,出现关键字的位置在其他Field里面,所以如果当前高亮的属性值中没有出现搜索关键字,则返回null.

排序

排序有几种方式,最常用的也就是相关度排序,把最有可能是用户需要的数据放在前面.由高到低排序,默认即是使用相关度排序.当然,我们也可以控制相关度排分的比重,比如我们想让一条记录的得分加倍,就要在建立索引时,加几句操作:

Document doc =  DocumentUtils. docConvert(article, Document. class );
// setBoost()方法需要一个float类型的参数,表示将相关度得分的因子增加多少倍
doc. setBoost(2F);
indexWriter. addDocument(doc);

这样加进去的索引,就会改变默认的相关度得分因子.如果我们想靠某个Field的值来排序也可以,默认是升序,如果是按某个Field的值来排序,那将不会生成相关度得分,因为没有必要.假设按ID进行升序排列:

Sort sort =  new  Sort(new  SortField(" id " ,SortField. INT));
TopDocs topDocs =  indexSearcher. search(query,null 100 ,sort);

IndexSearcher 的search()方法需要改成接收4个参数的重载方法,第二个参数需要一个Filter,这里不需要,就传入null,Sort需要知道按什么排序,由 SortField的第一个构造参数指明,第二参数指明类型,接收一个int值,由SortField对象的常量值表示.还可以加入第三个参数:

Sort sort =  new  Sort(new  SortField(" id " , SortField. INT, true ));

表示是否倒序排序.也可以指定多个排序规则.由多个SortField组成第1顺序、第2顺序...

过滤

使用Filter可以对搜索结果进行过滤,以获得更小范围的结果.使用Filter对性能的影响很大(有可能会使查询慢上百倍).假如我要看ID从5到15的:

// 第三个参数:是否包含最小值(5);第四个参数:是否包含最大值(15)
Filter filter =  NumericRangeFilter. newIntRange(" id " 5 15 true ,true );
// 这个时候就不需要第四个参数来指定排序了,search()这个方法有很多重载版本
TopDocs topDocs =  indexSearcher. search(query,filter, 100 );

光这样写还不行,会看不到结果.这是因为要过滤的值,如果是数字,需要以指定的格式存储才行,否则将按字符串的方式来比较.

对于这个问题,需要引出一个工具类:NumericUtils,这个工具类专门做数值与数值或字符串间的转换工作.要在对象到Document以及Document到对象的转换的时候进行,这是对于数值,如果是日期类型,需要使用这个工具类:DateTools.

注:这样做了以后,可能还是不行,对于使用NumericUtils把数字转换为字符串,一定要使用Index.NOT_ANALYZED,以保存能够正确的使用Filter

 

其他搜索

首先,有两种搜索方式:

  1. 使用QueryParser(或MultiFieldQueryParser)解析查询字符串的方式
  2. 自己直接创建Query子类的实例方式

由于可以使用Query对象的toString()方法打印出对应的查询语法,所以以第二种方式做的话,可以看相应的第一种查询方法:

/*     1.匹配所有对应的查询字符串为:          *:*
       Query   query   =   new   MatchAllDocsQuery();
      
       2.范围查询对应的查询字符串为:         id:[5   TO   15]
       Query   query   =   NumericRangeQuery.newIntRange("id",   5,   15,   true,   true);
           范围查询对应的查询字符串为:       id:{5   TO   15}
       Query   query   =   NumericRangeQuery.newIntRange("id",   5,   15,   false,false);
           范围查询对应的查询字符串为:        id:{5   TO   15]
       Query   query   =   NumericRangeQuery.newIntRange("id",   5,   15,   false,   true);
      
       3.关键词对应的查询字符串为:          title:lucene
       Query   query   =   new   TermQuery(new   Term("title",   "lucene"));
      
       4.通配符对应的查询字符串为:           title:lucen?
       Query   query   =   new   WildcardQuery(new   Term("title",   "lucen?"));
           通配符对应的查询字符串为:           title:lu*ne
       Query   query   =   new   WildcardQuery(new   Term("title",   "lu*ne"));
      
       5.短语对应的查询字符串为:             title:"lucene   ?   ?   工作"
       PhraseQuery   phraseQuery   =   new   PhraseQuery();
       phraseQuery.add(new   Term("title",   "lucene"),   0);   //   第一个词的索引是0
       phraseQuery.add(new   Term("title",   "工作"),   3);
           短语对应的查询字符串为:             title:"lucene   工作"~5
       phraseQuery.add(new   Term("title",   "lucene"));
       phraseQuery.add(new   Term("title",   "工作"));
       phraseQuery.setSlop(5);   //   指定这些词中间的间隔最多不会超过5个词
   
       6.布尔查询
            对应的查询语法两种:
            1.   +   代表MUST,-表示NOT
            2.   AND、OR、NOT,注意要全是大写
            3.   MUST           必须满足
                 MUST_NOT     
                 SHOULD           多个SHOULD一起用,是OR的关系
           
            Query   query1   =   new   TermQuery(new   Term("title",   "lucene"));
            Query   query2   =   NumericRangeQuery.newIntRange("id",   5,   15,   false,   true);
            对应的查询字符串为:          +title:lucene   +id:{5   TO   15]
            对应的查询字符串为:          title:lucene   AND   id:{5   TO   15]
            booleanQuery.add(query1,   Occur.MUST);
            booleanQuery.add(query2,   Occur.MUST);         
           
            对应的查询字符串为:          +title:lucene   -id:{5   TO   15]
            对应的查询字符串为:          title:lucene   NOT   id:{5   TO   15]
            booleanQuery.add(query1,   Occur.MUST);
            booleanQuery.add(query2,   Occur.MUST_NOT);
                      
            对应的查询字符串为:          title:lucene   id:{5   TO   15]
            对应的查询字符串为:          title:lucene   OR   id:{5   TO   15]
            booleanQuery.add(query1,   Occur.SHOULD);
            booleanQuery.add(query2,   Occur.SHOULD);
          ----------------------------------------------------
            MUST   +   SHOULD,与只有一个MUST效果相同
            SHOULD   +   MUST_NOT,这时SHOULD就相当MUST
            MUST_NOT   +   MUST_NOT,没有匹配结果,也不报错。
*/

猜你喜欢

转载自san-yun.iteye.com/blog/1577497
今日推荐