Lucene4.3開發之插曲之包容萬物

    原本lucene的內置分詞器,差很少能夠完成咱們的大部分分詞工做了,若是是英文文章那麼可使用StandardAnalyzer標準分詞器,WhitespaceAnalyzer空格分詞器,對於中文咱們則能夠選擇IK分詞器,Message4j,庖丁等分詞器。
java

    咱們先來看看下面的幾個需求
正則表達式

    仔細分析上面的需求,會以爲上面的需求很沒有意思,可是在特定的場合確實是存在這樣的需求的,看起來上面的需求很簡單,可是lucene裏面內置的分析器卻沒有一個支持這種變態的"無聊的"分詞需求,若是想要知足上面的需求,可能就須要咱們本身定製本身的分詞器了。
數據庫

    先來看第一個需求,單個字符切分,這就要無論你是the一個單詞仍是一個電話號碼仍是一段話仍是其餘各類特殊符號都要保留下來,進行單子切分,這種特細粒度的分詞,有兩種需求狀況,可能適應這兩種場景。
apache

一、100%的實現數據庫模糊匹配數組

二、對於某個電商網站筆記本的型號Y490,要求用戶不管輸入Y仍是四、九、0均可以找到這款筆記本ide

    這種單字切分確實能夠實現數據庫的百分百模糊檢索,可是同時也帶來了一些問題,若是這個域中是存電話號碼,或者身份證之類的與數字相關的信息,那麼這中分詞法,會形成這個域的倒排鏈表很是之長,反映到搜索上,就會出現中文檢索很快,而數字的檢索確實很是之慢的問題。緣由是由於數組只有0~9個字符,而漢字則遠遠比這個數量要大得多,因此在選用這種分詞時,仍是須要慎重的考慮下本身的業務場景到底適不適合這種分詞時,仍是要慎重的考慮下本身的業務場景到底適不適合這種分詞,不然就會可能出現一些問題。
測試

    在分析下2和3的需求,這種需求可能存在這麼一種狀況,就是某個字段裏存的內容是按照逗號或者空格,#號,或者是本身定義的一個字符串進行分割存儲的,而這種時候咱們可能就會想到一些很是簡單的作法,直接調用String類的splite方法進行打散,確實,這種方式是可行的,可是lucene裏面的結構某些狀況下,就可能不適合用字符串拆分的方法,而是要求咱們必須定義一個本身的分詞器來完成這種功能,由於涉及到一些參數須要傳一個分詞器或者索引金額檢索是都要使用分詞器來構造解析,因此有時候就必須得本身定義個專門處理這種狀況的分詞器了。網站

    下面開始給出代碼,首先針對第一個需求,單字切分,其實這個需求沒有什麼難的,只要熟悉lucene的Tokenizer就能夠輕鬆解決,咱們改寫Chinese Tokenizer來知足咱們的需求。
this

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

}

而後定義個本身的分詞器 spa

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@#$+-)(*&^.,/"; 

對於第二種需求咱們要模仿空格分詞器的的原理,代碼以下 

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("我分割你分割他分割"));

我
你
他
相關文章
相關標籤/搜索