文档章节

  lucene4.7 分词器(三) 之特殊分词器

一枚Sir
 一枚Sir
发布于 2014/04/10 18:29
字数 1920
阅读 537
收藏 2

一些特殊的分词需求,在此做个总结。本来的Lucene的内置的分词器,差不多可以完成我们的大部分分词工作了,如果是英文文章那么可以使用StandardAnalyzer标准分词器,WhitespaceAnalyzer空格分词器,对于中文我们则可以选择IK分词器,Messeg4j,庖丁等分词器。 

我们先来看看下面的几个需求 

编号 需求分析
1 按单个字符进行分词无论是数字,字母还是特殊符号
2 按特定的字符进行分词,类似String中spilt()方法
3 按照某个字符或字符串进行分词

仔细分析下上面的需求,会觉得上面的需求很没意思,但是在特定的场合下确实是存在这样的需求的,看起来上面的需求很简单,但是lucene里面内置的分析器却没有一个支持这种变态的"无聊的"分词需求,如果想要满足上面的需求,可能就需要我们自己定制自己的分词器了。 


先来看第一个需求,单个字符切分,这就要不管你是the一个单词还是一个电话号码还是一段话还是其他各种特殊符号都要保留下来,进行单字切分,这种特细粒度的分词,有两种需求情况,可能适应这两种场景 
(-)100%的实现数据库模糊匹配 
(=)对于某个电商网站笔记本的型号Y490,要求用户无论输入Y还是4,9,0都可以找到这款笔记本 


这种单字切分确实可以实现数据库的百分百模糊检索,但是同时也带来了一些问题,如果这个域中是存电话号码,或者身份证之类的与数字的相关的信息,那么这种分词法,会造成这个域的倒排链表非常之长,反映到搜索上,就会出现中文检索很快,而数字的检索确实非常之慢的问题。原因是因为数字只有0-9个字符,而汉字则远远比这个数量要大的多,所以在选用这种分词时,还是要慎重的考虑下自己的业务场景到底适不适合这种分词,否则就会可能出一些问题。 

再来分析下2和3的需求,这种需求可能存在这么一种情况,就是某个字段里存的内容是按照逗号或者空格,#号,或者是自己定义的一个字符串进行分割存储的,而这种时候我们可能就会想到一些非常简单的做法,直接调用String类的spilt方法进行打散,确实,这种方式是可行的,但是lucene里面的结构某些情况下,就可能不适合用字符串拆分的方法,而是要求我们必须定义一个自己的分词器来完成这种功能,因为涉及到一些参数需要传一个分词器或者索引和检索时都要使用分词器来构造解析,所以有时候就必须得自己定义个专门处理这种情况的分词器了。 

好了,散仙不在唠叨了,下面开始给出代码,首先针对第一个需求,单字切分,其实这个需求没什么难的,只要熟悉lucene的Tokenizer就可以轻松解决,我们改写ChineseTokenizer来满足我们的需求.
package com.piaoxuexianjing.cn;

import java.io.IOException;
import java.io.Reader;

import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.util.AttributeSource.AttributeFactory;

public class China extends Tokenizer {
    
     public China(Reader in) {
          super(in);
        }

        public China(AttributeFactory factory, Reader in) {
          super(factory, in);
        }
           
        private int offset = 0, bufferIndex=0, dataLen=0;
        private final static int MAX_WORD_LEN = 255;
        private final static int IO_BUFFER_SIZE = 1024;
        private final char[] buffer = new char[MAX_WORD_LEN];
        private final char[] ioBuffer = new char[IO_BUFFER_SIZE];


        private int length;
        private int start;

        private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
        private final OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class);
        
        private final void push(char c) {

            if (length == 0) start = offset-1;            // start of token
            buffer[length++] = Character.toLowerCase(c);  // buffer it

        }

        private final boolean flush() {

            if (length>0) {
                //System.out.println(new String(buffer, 0,
                //length));
              termAtt.copyBuffer(buffer, 0, length);
              offsetAtt.setOffset(correctOffset(start), correctOffset(start+length));
              return true;
            }
            else
                return false;
        }

        @Override
        public boolean incrementToken() throws IOException {
            clearAttributes();

            length = 0;
            start = offset;


            while (true) {

                final char c;
                offset++;

                if (bufferIndex >= dataLen) {
                    dataLen = input.read(ioBuffer);
                    bufferIndex = 0;
                }

                if (dataLen == -1) {
                  offset--;
                  return flush();
                } else
                    c = ioBuffer[bufferIndex++];


                switch(Character.getType(c)) {

                case Character.DECIMAL_DIGIT_NUMBER://注意此部分不过滤一些熟悉或者字母
                case Character.LOWERCASE_LETTER://注意此部分
                case Character.UPPERCASE_LETTER://注意此部分
//                    push(c);
//                    if (length == MAX_WORD_LEN) return flush();
//                    break;
             
                case Character.OTHER_LETTER:
                    if (length>0) {
                        bufferIndex--;
                        offset--;
                        return flush();
                    }
                    push(c);
                    return flush();

                default:
                    if (length>0) return flush();
                     
                        break;
                    
                }
            }
        }
        
        @Override
        public final void end() {
          // set final offset
          final int finalOffset = correctOffset(offset);
          this.offsetAtt.setOffset(finalOffset, finalOffset);
        }

        @Override
        public void reset() throws IOException {
          super.reset();
          offset = bufferIndex = dataLen = 0;
        }

}

然后定义个自己的分词器

package com.piaoxuexianjing.cn;

import java.io.Reader;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.Tokenizer;

/**
 * @author 三劫散仙
 * 单字切分
 * 
 * **/
public class MyChineseAnalyzer extends Analyzer {

    @Override
    protected TokenStreamComponents createComponents(String arg0, Reader arg1) {
       
        Tokenizer token=new China(arg1);
        
        return new TokenStreamComponents(token);
    }
    
    
    
    

}

下面我们来看单字切词效果,对于字符串 
String text="天气不错132abc@#$+-)(*&^.,/";

天
气
不
错
1
3
2
a
b
c
@
#
$
+
-
)
(
*
&
^
.
,
/

对于第二种需求我们要模仿空格分词器的的原理,代码如下

package com.splitanalyzer;

import java.io.Reader;

import org.apache.lucene.analysis.util.CharTokenizer;
import org.apache.lucene.util.Version;

/***
 *
 *@author 三劫散仙
 *拆分char Tokenizer
 * 
 * */
public class SpiltTokenizer extends CharTokenizer {
 
       char c;
    public SpiltTokenizer(Version matchVersion, Reader input,char c) {
        super(matchVersion, input);
        // TODO Auto-generated constructor stub
        this.c=c;
    }

    @Override
    protected boolean isTokenChar(int arg0) {
        return arg0==c?false:true ;
    }
    
    
    

}

然后在定义自己的分词器

package com.splitanalyzer;

import java.io.Reader;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.util.Version;

/**
 * @author 三劫散仙
 * 自定义单个char字符分词器
 * **/
public class SplitAnalyzer extends Analyzer{
    char c;//按特定符号进行拆分
    
    public SplitAnalyzer(char c) {
        this.c=c;
    }

    @Override
    protected TokenStreamComponents createComponents(String arg0, Reader arg1) {
        // TODO Auto-generated method stub
        return  new TokenStreamComponents(new SpiltTokenizer(Version.LUCENE_43, arg1,c));
    }
    

}

下面看一些测试效果

package com.splitanalyzer;

import java.io.StringReader;

import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;

/**
 * 测试的demo
 * 
 * **/
public class Test {
    
    public static void main(String[] args)throws Exception {
         SplitAnalyzer analyzer=new SplitAnalyzer('#');
             //SplitAnalyzer analyzer=new SplitAnalyzer('+');
        //PatternAnalyzer analyzer=new PatternAnalyzer("abc");
        TokenStream ts=    analyzer.tokenStream("field", new StringReader("我#你#他"));
       // TokenStream ts=    analyzer.tokenStream("field", new StringReader("我+你+他"));
        CharTermAttribute term=ts.addAttribute(CharTermAttribute.class);
        ts.reset();
        while(ts.incrementToken()){
            System.out.println(term.toString());
        }
        ts.end();
        ts.close();
         
    }

}
我
你
他

到这里,可能一些朋友已经看不下去了,代码太多太臃肿了,有没有一种通用的办法,解决此类问题,散仙的回答是肯定的,如果某些朋友,连看到这部分的耐心都没有的话,那么,不好意思,你只能看到比较低级的解决办法了,当然能看到这部分的道友们,散仙带着大家来看一下比较通用解决办法,这个原理其实是基于正则表达式的,所以由此看来,正则表达式在处理文本字符串上面有其独特的优势。下面我们要做的就是改写自己的正则解析器,代码非常精简,功能却是很强大的,上面的3个需求都可以解决,只需要传入不用的参数即可。

package com.splitanalyzer;

import java.io.Reader;
import java.util.regex.Pattern;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.pattern.PatternTokenizer;

/**
 * @author 三劫散仙
 * 自定义分词器
 * 针对单字切
 * 单个符号切分
 * 多个符号组合切分
 * 
 * **/
public class PatternAnalyzer  extends Analyzer {
    
    String regex;//使用的正则拆分式
    public PatternAnalyzer(String regex) {
         this.regex=regex;
    }

    @Override
    protected TokenStreamComponents createComponents(String arg0, Reader arg1) {
        return new TokenStreamComponents(new PatternTokenizer(arg1, Pattern.compile(regex),-1));
    }
}

我们来看下运行效果:

package com.splitanalyzer;

import java.io.StringReader;

import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;

/**
 * 测试的demo
 * 
 * **/
public class Test {
    
    public static void main(String[] args)throws Exception {
       //  SplitAnalyzer analyzer=new SplitAnalyzer('#');
         PatternAnalyzer analyzer=new PatternAnalyzer("");
         //空字符串代表单字切分  
        TokenStream ts=    analyzer.tokenStream("field", new StringReader("我#你#他"));
        CharTermAttribute term=ts.addAttribute(CharTermAttribute.class);
        ts.reset();
        while(ts.incrementToken()){
            System.out.println(term.toString());
        }
        ts.end();
        ts.close();
         
    }

}

输出效果:

我
#
你
#
他

传入#号参数

PatternAnalyzer analyzer=new PatternAnalyzer("#");

输出效果:

我
你
他

传入任意长度的字符串参数

 PatternAnalyzer analyzer=new PatternAnalyzer("分割");
TokenStream ts=    analyzer.tokenStream("field", new StringReader("我分割你分割他分割"));

输出效果:

我
你
他

© 著作权归作者所有

一枚Sir
粉丝 119
博文 209
码字总数 350904
作品 0
朝阳
架构师
私信 提问
lucene4.7 分词器(三)

前面几章笔者把Lucene基本入门的任督二脉给打通了,从此篇开始,就开始进行Lucene的进阶开发了,那么首先摆在我们面前的第一个必须要解决的问题,就是关于中文分词的问题,因为Lucene毕竟是国...

一枚Sir
2014/04/10
1K
0
lucene StandardAnalyzer

lucene 版本5.2.1 在使用lucene StandardAnalyzer(标准分词器)针对生物文本进行分词索引的时候,”Lucene-PMC“像这样的词汇会被拆分为: 1:[Lucene]:(0-->6): 2:[PMC]:(7-->10): 英文标准分...

yimi
2016/11/03
118
0
Lucene4.7 索引和检索的常用API(二)

前面几篇笔者已经把Lucene的最基本的入门,介绍完了,本篇就对Lucene基本的知识做一个总结,以便于加深对Lucene基本API组件的理解。 为了方便对比学习,下面给出表格数据 索引期间使用的API...

一枚Sir
2014/04/10
1K
0
Solr6.3.0 采用word1.3分词器 高亮显示异常

@杨尚川 你好,想跟你请教个问题: 我在Solr6.3.0上采用word.1.3.jar作为分词器,相应的配置也都按github上的改了,分词没问题,但是高亮出现了很多偏差,在网上搜了很多方案,基本都是说分词...

清风-蓝魔泪
2016/12/13
459
4
Lucene4.3开发之第四步之脱胎换骨(四)

前面几章把lucene基本入门的任督二脉给打通了,从此篇开始,就开始进行lucene的进阶开发了,那么首先摆在我们前面的第一个必须要解决的问题,就是关于中文分词的问题,因为lucene毕竟是国外大...

heroShane
2014/02/21
261
1

没有更多内容

加载失败,请刷新页面

加载更多

golang-字符串-地址分析

demo package mainimport "fmt"func main() {str := "map.baidu.com"fmt.Println(&str, str)str = str[0:5]fmt.Println(&str, str)str = "abc"fmt.Println(&s......

李琼涛
41分钟前
3
0
Spring Boot WebFlux 增删改查完整实战 demo

03:WebFlux Web CRUD 实践 前言 上一篇基于功能性端点去创建一个简单服务,实现了 Hello 。这一篇用 Spring Boot WebFlux 的注解控制层技术创建一个 CRUD WebFlux 应用,让开发更方便。这里...

泥瓦匠BYSocket
今天
6
0
从0开始学FreeRTOS-(列表与列表项)-3

FreeRTOS列表&列表项的源码解读 第一次看列表与列表项的时候,感觉很像是链表,虽然我自己的链表也不太会,但是就是感觉很像。 在FreeRTOS中,列表与列表项使用得非常多,是FreeRTOS的一个数...

杰杰1号
今天
4
0
Java反射

Java 反射 反射是框架设计的灵魂(使用的前提条件:必须先得到代表的字节码的 Class,Class 类 用于表示.class 文件(字节码)) 一、反射的概述 定义:JAVA 反射机制是在运行状态中,对于任...

zzz1122334
今天
4
0
聊聊nacos的LocalConfigInfoProcessor

序 本文主要研究一下nacos的LocalConfigInfoProcessor LocalConfigInfoProcessor nacos-1.1.3/client/src/main/java/com/alibaba/nacos/client/config/impl/LocalConfigInfoProcessor.java p......

go4it
昨天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部