ICTCLAS分词器与Lucene4.9的结合

时间:2022-07-07 17:30:25

一直以来喜欢搜索方向,虽然未能如愿以偿,但是依旧保持着那份*。还记得那个暑假、那间实验室、那一群人,一切的一切早已随风而去。踏上新的征程,我早已不是曾经的自己。面对三分技术七分业务的环境,我选择了沉淀。社会就是个大机器,我们只是个小螺丝,容不下半点扭扭捏捏。人终究归属于一个时代的产物,也终将被时代所抛弃。言归正题,在lucene加入自定义的分词器,需要继承Analyzer类,实现createComponents方法,同时定义Tokenzier类用于记录所需建立索引的词以及其在文章的位置,这里继承SegmentingTokenizerBase类,需要实现setNextSentence与incrementWord两个方法。其中,setNextSentence设置下一个句子,在多域(Filed)分词索引时,setNextSentence就是设置下一个域的内容,可以通过new String(buffer, sentenceStart, sentenceEnd - sentenceStart)获取。而incrementWord方法则是记录每个单词以及它的位置,需要注意一点就是要在前面加clearAttributes(),否则可能出现first position increment must be > 0...错误。以ICTCLAS分词器为例,下面贴上个人代码,希望能给大家带来帮助,不足之处,多多拍砖。


import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.core.LowerCaseFilter;
import org.apache.lucene.analysis.en.PorterStemFilter;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.util.Version;

/**
 * 中科院分词器 继承Analyzer类,实现其 tokenStream方法
 * 
 * @author ckm
 * 
 */
public class ICTCLASAnalyzer extends Analyzer {

	/**
	 * 该方法主要是将文档转变成lucene建立索 引所需的TokenStream对象
	 * 
	 * @param fieldName
	 *            文件名
	 * @param reader
	 *            文件的输入流
	 */
	@Override
	protected TokenStreamComponents createComponents(String fieldName, Reader reader) {
		try {
			System.out.println(fieldName);
		    final Tokenizer tokenizer = new ICTCLASTokenzier(reader);
		    TokenStream stream = new PorterStemFilter(tokenizer);
		    stream = new LowerCaseFilter(Version.LUCENE_4_9, stream);
		    stream = new PorterStemFilter(stream);
			return new TokenStreamComponents(tokenizer,stream);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
		
	}
	
	
	public static void main(String[] args) throws Exception {  
        Analyzer analyzer = new ICTCLASAnalyzer();  
        String str = "黑客技术";  
        TokenStream ts = analyzer.tokenStream("field", new StringReader(str));  
        CharTermAttribute c = ts.addAttribute(CharTermAttribute.class);  
        ts.reset();  
        while (ts.incrementToken()) {  
            System.out.println(c.toString());  
        }  
  
        ts.end();  
        ts.close();  
    }  

	
}


import java.io.IOException;
import java.io.Reader;
import java.text.BreakIterator;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.util.SegmentingTokenizerBase;
import org.apache.lucene.util.AttributeFactory;


/**
 * 
 * 继承lucene的SegmentingTokenizerBase,重载其setNextSentence与
 * incrementWord方 法,记录所需建立索引的词以及其在文章的位置
 * 
 * @author ckm
 * 
 */
public class ICTCLASTokenzier extends SegmentingTokenizerBase {
	
	private static final BreakIterator sentenceProto = BreakIterator.getSentenceInstance(Locale.ROOT);
	
	private final CharTermAttribute termAttr= addAttribute(CharTermAttribute.class);// 记录所需建立索引的词
	
	private final OffsetAttribute offAttr = addAttribute(OffsetAttribute.class);// 记录所需建立索引的词在文章中的位置
	
	private ICTCLASDelegate ictclas;// 分词系统的委托对象
	
	private Iterator<String> words;// 文章分词后形成的单词
	
	private int offSet= 0;// 记录最后一个词元的结束位置
	
	
	/**
	 * 构造函数
	 * 
	 * @param segmented    分词后的结果
	 * @throws IOException 
	 */
	protected ICTCLASTokenzier(Reader reader) throws IOException {
		this(DEFAULT_TOKEN_ATTRIBUTE_FACTORY, reader);
	}
	
	protected ICTCLASTokenzier(AttributeFactory factory, Reader reader) throws IOException {
		super(factory, reader,sentenceProto);
		ictclas = ICTCLASDelegate.getDelegate();
	  
	}

	@Override
	protected void setNextSentence(int sentenceStart, int sentenceEnd) {
		// TODO Auto-generated method stub
		String sentence = new String(buffer, sentenceStart, sentenceEnd - sentenceStart);
		String result=ictclas.process(sentence);
		String[] array = result.split("\\s");
		if(array!=null){
			List<String> list = Arrays.asList(array);
			words=list.iterator();
		}
		offSet= 0;
	}

	@Override
	protected boolean incrementWord() {
		// TODO Auto-generated method stub
		if (words == null || !words.hasNext()) {
		    return false;
		} else {
			String t = words.next();
			while(t.equals("")||StopWordFilter.filter(t)){ //这里主要是为了过滤空白字符以及停用词
				                                          //StopWordFilter为自定义停用词过滤类  
				if (t.length() == 0)
					offSet++;
				else
					offSet+= t.length();
				t =words.next();
			}
			if (!t.equals("") && !StopWordFilter.filter(t)) {
				clearAttributes();
				termAttr.copyBuffer(t.toCharArray(), 0, t.length());
				offAttr.setOffset(correctOffset(offSet), correctOffset(offSet=offSet+ t.length()));
				return true;
			} 
			return false;
		}
	}
	
	/**
	 * 重置
	 */
	public void reset() throws IOException {
		super.reset();
		offSet= 0;
	}

	public static void main(String[] args) throws IOException {
		String content = "宝剑锋从磨砺出,梅花香自苦寒来!";
		String seg = ICTCLASDelegate.getDelegate().process(content);
		//ICTCLASTokenzier test = new ICTCLASTokenzier(seg);
		//while (test.incrementToken());
	}
	
}



import java.io.File;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import ICTCLAS.I3S.AC.ICTCLAS50;

/**
 * 中科院分词系统代理类
 * 
 * @author ckm
 * 
 */
public class ICTCLASDelegate {

	private static final String userDict = "userDict.txt";// 用户词典
	
	private final static Charset charset = Charset.forName("gb2312");// 默认的编码格式
	
	private static String ictclasPath =System.getProperty("user.dir");
	
	private static String dirConfigurate = "ICTCLASConf";// 配置文件所在目录名
	
	private static String configurate = ictclasPath + File.separator+ dirConfigurate;// 配置文件所在目录的绝对路径
	
	private static int wordLabel = 2;// 词性标注类型(北大二级标注集)
	
	private static ICTCLAS50 ictclas;// 中科院分词系统的jni接口对象
	
	private static ICTCLASDelegate instance = null;
	
	private ICTCLASDelegate(){ }
	

	/**
	 * 初始化ICTCLAS50对象
	 * 
	 * @return ICTCLAS50对象初始化化是否成功
	 */
	public boolean init() {
		ictclas = new ICTCLAS50();
		boolean bool = ictclas.ICTCLAS_Init(configurate
				.getBytes(charset));
		if (bool == false) {
			System.out.println("Init Fail!");
			return false;
		}
		// 设置词性标注集(0 计算所二级标注集,1 计算所一级标注集,2 北大二级标注集,3 北大一级标注集)
		ictclas.ICTCLAS_SetPOSmap(wordLabel);
		importUserDictFile(configurate + File.separator + userDict);// 导入用户词典
		ictclas.ICTCLAS_SaveTheUsrDic();// 保存用户字典
		return true;
	}

	/**
	 * 将编码格式转换为分词系统识别的类型
	 * 
	 * @param charset
	 *            编码格式
	 * @return 编码格式对应的数字
	 **/
	public static int getECode(Charset charset) {
		String name = charset.name();
		if (name.equalsIgnoreCase("ascii"))
			return 1;
		if (name.equalsIgnoreCase("gb2312"))
			return 2;
		if (name.equalsIgnoreCase("gbk"))
			return 2;
		if (name.equalsIgnoreCase("utf8"))
			return 3;
		if (name.equalsIgnoreCase("utf-8"))
			return 3;
		if (name.equalsIgnoreCase("big5"))
			return 4;
		return 0;
	}

	/**
	 * 该方法的作用是导入用户字典
	 * 
	 * @param path
	 *            用户词典的绝对路径
	 * @return 返回导入的词典的单词个数
	 */
	public int importUserDictFile(String path) {
		System.out.println("导入用户词典");
		return ictclas.ICTCLAS_ImportUserDictFile(
				path.getBytes(charset), getECode(charset));
	}

	/**
	 * 该方法的作用是对字符串进行分词
	 * 
	 * @param source
	 *            所要分词的源数据
	 * @return 分词后的结果
	 */
	public String process(String source) {
		return process(source.getBytes(charset));
	}
	
	public String process(char[] chars){
	   CharBuffer cb = CharBuffer.allocate (chars.length);
	   cb.put (chars);
	   cb.flip ();
	   ByteBuffer bb = charset.encode (cb);
	   return process(bb.array());
	   
	}
	
	public String process(byte[] bytes){
		if(bytes==null||bytes.length<1)
			return null;
		byte nativeBytes[] = ictclas.ICTCLAS_ParagraphProcess(bytes, 2, 0);
		String nativeStr = new String(nativeBytes, 0,
				nativeBytes.length-1, charset);
		return nativeStr;
	}

	/**
	 * 获取分词系统代理对象
	 * 
	 * @return 分词系统代理对象
	 */
	public static ICTCLASDelegate getDelegate() {
		if (instance == null) {
			synchronized (ICTCLASDelegate.class) {
				instance = new ICTCLASDelegate();
				instance.init();
			}
		}
		return instance;
	}

	/**
	 * 退出分词系统
	 * 
	 * @return 返回操作是否成功
	 */
	public boolean exit() {
		return ictclas.ICTCLAS_Exit();
	}

	public static void main(String[] args) {
		String str="结婚的和尚未结婚的";
		ICTCLASDelegate id = ICTCLASDelegate.getDelegate();
		String result = id.process(str.toCharArray());
		System.out.println(result.replaceAll(" ", "-"));
	}

}


import java.util.Iterator;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 停用词过滤器
 * 
 * @author ckm
 * 
 */
public class StopWordFilter {

	private static Set<String> chineseStopWords = null;// 中文停用词集
	private static Set<String> englishStopWords = null;// 英文停用词集
	static {
		init();
	}

	/**
	 * 初始化中英文停用词集
	 */
	public static void init() {
		LoadStopWords lsw = new LoadStopWords();
		chineseStopWords = lsw.getChineseStopWords();
		englishStopWords = lsw.getEnglishStopWords();
	}

	/**
	 * 判断关键字的类型以及判断其是否为停用词 注意:暂时只考虑中文,英文,中英混合, 中数混合,英数混合这五种类型,其中中英 混合,
	 * 中数混合,英数混合还没特定的停用 词库或语法规则对其进行判别
	 * 
	 * @param word
	 *            关键字
	 * @return true表示是停用词
	 */
	public static boolean filter(String word) {
		Pattern chinese = Pattern.compile("^[\u4e00-\u9fa5]+$");// 中文匹配
		Matcher m1 = chinese.matcher(word);
		if (m1.find())
			return chineseFilter(word);
		Pattern english = Pattern.compile("^[A-Za-z]+$");// 英文匹配
		Matcher m2 = english.matcher(word);
		if (m2.find())
			return englishFilter(word);
		Pattern chineseDigit = Pattern.compile("^[\u4e00-\u9fa50-9]+$");// 中数匹配
		Matcher m3 = chineseDigit.matcher(word);
		if (m3.find())
			return chineseDigitFilter(word);
		Pattern englishDigit = Pattern.compile("^[A-Za-z0-9]+$");// 英数匹配
		Matcher m4 = englishDigit.matcher(word);
		if (m4.find())
			return englishDigitFilter(word);
		Pattern englishChinese = Pattern.compile("^[A-Za-z\u4e00-\u9fa5]+$");// 中英匹配,这个必须在中文匹配与英文匹配之后
		Matcher m5 = englishChinese.matcher(word);
		if (m5.find())
			return englishChineseFilter(word);
		return true;
	}

	/**
	 * 判断关键字是否为中文停用词
	 * 
	 * @param word
	 *            关键字
	 * @return true表示是停用词
	 */
	public static boolean chineseFilter(String word) {
		// System.out.println("中文停用词判断");
		if (chineseStopWords == null || chineseStopWords.size() == 0)
			return false;
		Iterator<String> iterator = chineseStopWords.iterator();
		while (iterator.hasNext()) {
			if (iterator.next().equals(word))
				return true;
		}
		return false;
	}

	/**
	 * 判断关键字是否为英文停用词
	 * 
	 * @param word
	 *            关键字
	 * @return true表示是停用词
	 */
	public static boolean englishFilter(String word) {
		// System.out.println("英文停用词判断");
		if (word.length() <= 2)
			return true;
		if (englishStopWords == null || englishStopWords.size() == 0)
			return false;
		Iterator<String> iterator = englishStopWords.iterator();
		while (iterator.hasNext()) {
			if (iterator.next().equals(word))
				return true;
		}
		return false;
	}

	/**
	 * 判断关键字是否为英数停用词
	 * 
	 * @param word
	 *            关键字
	 * @return true表示是停用词
	 */
	public static boolean englishDigitFilter(String word) {
		return false;

	}

	/**
	 * 判断关键字是否为中数停用词
	 * 
	 * @param word
	 *            关键字
	 * @return true表示是停用词
	 */
	public static boolean chineseDigitFilter(String word) {
		return false;

	}

	/**
	 * 判断关键字是否为英中停用词
	 * 
	 * @param word
	 *            关键字
	 * @return true表示是停用词
	 */
	public static boolean englishChineseFilter(String word) {
		return false;

	}

	public static void main(String[] args) {
		/*
		 * Iterator<String> iterator=
		 * StopWordFilter.chineseStopWords.iterator(); int n=0;
		 * while(iterator.hasNext()){ System.out.println(iterator.next()); n++;
		 * } System.out.println("总单词量:"+n);
		 */
		boolean bool = StopWordFilter.filter("宝剑");
		System.out.println(bool);
	}

}

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * 加载停用词文件
 * 
 * @author ckm
 * 
 */

public class LoadStopWords {

	private Set<String> chineseStopWords = null;// 中文停用词集

	private Set<String> englishStopWords = null;// 英文停用词集

	/**
	 * 获取中文停用词集
	 * 
	 * @return 中文停用词集Set<String>类型
	 */
	public Set<String> getChineseStopWords() {
		return chineseStopWords;
	}

	/**
	 * 设置中文停用词集
	 * 
	 * @param chineseStopWords
	 *            中文停用词集Set<String>类型
	 */
	public void setChineseStopWords(Set<String> chineseStopWords) {
		this.chineseStopWords = chineseStopWords;
	}

	/**
	 * 获取英文停用词集
	 * 
	 * @return 英文停用词集Set<String>类型
	 */
	public Set<String> getEnglishStopWords() {
		return englishStopWords;
	}

	/**
	 * 设置英文停用词集
	 * 
	 * @param englishStopWords
	 *            英文停用词集Set<String>类型
	 */
	public void setEnglishStopWords(Set<String> englishStopWords) {
		this.englishStopWords = englishStopWords;
	}

	/**
	 * 加载停用词库
	 */
	public LoadStopWords() {
		chineseStopWords = loadStopWords(this.getClass().getResourceAsStream(
				"ChineseStopWords.txt"));
		englishStopWords = loadStopWords(this.getClass().getResourceAsStream(
				"EnglishStopWords.txt"));
	}

	/**
	 * 从停用词文件中加载停用词, 停用词文件是普通GBK编码的文本文件, 每一行 是一个停用词,注释利用“//”, 停用词中包括中文标点符号,
	 * 中文空格, 以及使用率太高而对索引意义不大的词。
	 * 
	 * @param input
	 *            停用词文件流
	 * @return 停用词组成的HashSet
	 */
	public static Set<String> loadStopWords(InputStream input) {
		String line;
		Set<String> stopWords = new HashSet<String>();
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(input,
					"GBK"));
			while ((line = br.readLine()) != null) {
				if (line.indexOf("//") != -1) {
					line = line.substring(0, line.indexOf("//"));
				}
				line = line.trim();
				if (line.length() != 0)
					stopWords.add(line.toLowerCase());
			}
			br.close();
		} catch (IOException e) {
			System.err.println("不能打开停用词库!!");
		}
		return stopWords;
	}

	public static void main(String[] args) {
		LoadStopWords lsw = new LoadStopWords();
		Iterator<String> iterator = lsw.getEnglishStopWords().iterator();
		int n = 0;
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
			n++;
		}
		System.out.println("总单词量:" + n);
	}

}

这里需要ChineseStopWords.txt 与EnglishStopWords.txt分别存放中文与英文停用词,这里不知道怎么上传,还有就是ICTCLAS的基本文件。

完整项目下载地址:http://download.csdn.net/detail/km1218/7754907