Analyzer通过对文本的分析来建立TokenStreams(分词数据流)。TokenStream是由一个个Token(分词组成的数据流)。所以说Analyzer就代表着一个从文本数据中抽取索引词(Term)的一种策略。TokenStream即是从Document的域(field)中或者查询条件中抽取一个个分词而组成的一个数据流。TokenSteam中是一个个的分词,而每个分词又是由一个个的属性(Attribute)组成。对于所有的分词来说,每个属性只有一个实例。这些属性都保存在AttributeSource中,而AttributeSource正是TokenStream的父类。
8.PositionLengthAttributeImpl:Token所占用的位置个数
那么这个属性有什么用呢,用处很大的。加入我们想搜索一个短语student apples(假如有这个短语)。很显然,用户是要搜索出student apples紧挨着出现的文档。这个时候我们找到了某一篇文档(比如上面例子的字符串)都含有student apples。但是由于apples的PositionIncrementAttribute值是3,说明肯定没有紧挨着。
介绍几个类:
Tokenizer:接受Reader字符流,将Reader进行分词操作, extends TokenStream。
TokenFilter:将分词的语汇单元进行过滤, extends TokenStream。
TokenStream:分词器处理完毕后得到的一个流,存储了分词的各种信息。
分词流程:Read -----> Tokenizer -----> TokenFilter.0 -----> … -----> TokenFilter.n -----> TokenStream
几种分词器介绍
StandardAnalyzer:标准分词器,如果用来处理中文,只是将其分成单个汉字,并不存在任何语义或词性。
StopAnalyzer:被忽略的词分词器,被忽略的词就是在分词结果中,被丢弃的字符串,如标点、空格等。
SimpleAnalyzer:简单分词器,一句话就是一个词,遇到标点、空格等,就将其之前的内容当作一个词。
WhitespaceAnalyzer:空格分词,这个分词技术就相当于按照空格简单的切分字符串。
mmseg4j: 用Chih-Hao Tsai 的MMSeg算法实现的中文分词器,并实现lucene的analyzer和solr的TokenizerFactory以方便在Lucene和Solr中使用。
由于mmseg4j暂不支持lucene5.0。以下示例均为lucene3.5版本。
public class AnalyzerUtils { public static void displayToken(String str, Analyzer analyzer) { try { TokenStream tokenStream = analyzer.tokenStream("---", new StringReader(str)); // 创建一个属性,这个属性会添加流中,随着这个TokenStream增加 CharTermAttribute charTerm = tokenStream .addAttribute(CharTermAttribute.class); tokenStream.reset();// 必须先调用reset方法,否则会报java.lang.IllegalStateException while (tokenStream.incrementToken()) { System.out.print("" + charTerm + "|"); } tokenStream.end(); tokenStream.close(); System.out.println(); } catch (Exception e) { e.printStackTrace(); } } public static void displayAllTokenInfo(String str, Analyzer analyzer) { try { TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(str)); // 位置增量的属性,存储语汇单元之间的距离,它表示tokenStream中的当前token与前一个token在实际的原文本中相隔的词语数量 PositionIncrementAttribute pos = tokenStream .addAttribute(PositionIncrementAttribute.class); // 每个语汇单元的位置偏移量 OffsetAttribute offset = tokenStream .addAttribute(OffsetAttribute.class); // 存储每一个语汇单元的信息(分词单元信息),保存Token对应的term文本 CharTermAttribute charTerm = tokenStream .addAttribute(CharTermAttribute.class); // 使用的分词器的类型信息,分词的词汇类型,默认值为“word” TypeAttribute type = tokenStream.addAttribute(TypeAttribute.class); tokenStream.reset(); for (; tokenStream.incrementToken();) { System.out.print(pos.getPositionIncrement() + ": "); System.out.print("|" + charTerm + "|" + "---offset[" + offset.startOffset() + "-" + offset.endOffset() + "]---type:" + type.type() + "\n"); } } catch (Exception e) { e.printStackTrace(); } } }
/** * 自定义停用词过滤分词器 */ public class MyStopAnalyzer extends Analyzer { private Set stops; public MyStopAnalyzer(String[] sws) { // 会自动将字符串数组转换为Set stops = StopFilter.makeStopSet(Version.LUCENE_35, sws, true); // 将原有的停用词加入到现在的停用词 stops.addAll(StopAnalyzer.ENGLISH_STOP_WORDS_SET); } public TokenStream tokenStream(String str, Reader reader) { // 为这个分词器设定过滤链和Tokenizer return new StopFilter(Version.LUCENE_35, new LowerCaseFilter( Version.LUCENE_35, new LetterTokenizer(Version.LUCENE_35, reader)), stops); } }
/** * 自定义同义词过滤分词器 */ public class MySameAnalyzer extends Analyzer { @Override public TokenStream tokenStream(String arg0, Reader reader) { // TODO Auto-generated method stub Dictionary dic = Dictionary.getInstance("data/"); return new MySameTokenFilter(new MMSegTokenizer(new MaxWordSeg(dic), reader)); } class MySameTokenFilter extends TokenFilter { private CharTermAttribute charTerm = null; private PositionIncrementAttribute pos = null; private AttributeSource.State current; private Stack<String> sames = null; private Map<String, String[]> map = null; protected MySameTokenFilter(TokenStream input) { super(input); charTerm = this.addAttribute(CharTermAttribute.class); pos = this.addAttribute(PositionIncrementAttribute.class); sames = new Stack<String>(); map = new HashMap<String, String[]>(); map.put("中国", new String[] { "天朝", "大陆" }); } @Override public boolean incrementToken() throws IOException { if (sames.size() > 0) { // 将元素出栈,并且获取这个同义词 String str = sames.pop(); // 还原状态 restoreState(current); charTerm.setEmpty(); charTerm.append(str); // 设置位置0 pos.setPositionIncrement(0); return true; } if (!this.input.incrementToken()) return false; if (addSames(charTerm.toString())) { // 如果有同义词将当前状态先保存 current = captureState(); } return true; } private boolean addSames(String string) { String[] sws = map.get(string); if (sws != null) { for (String str : sws) { sames.push(str); } return true; } return false; } } }
@Test public void test01() { String txt = "this is my blog,我来自湖北,武汉"; Analyzer a1 = new StandardAnalyzer(Version.LUCENE_35); Analyzer a2 = new StopAnalyzer(Version.LUCENE_35); Analyzer a3 = new SimpleAnalyzer(Version.LUCENE_35); Analyzer a4 = new WhitespaceAnalyzer(Version.LUCENE_35); System.out.println(AnalyzerUtils.class.getResource("/")); Analyzer a5 = new MMSegAnalyzer(new File("E:/sina_workspace/Lucene/data")); AnalyzerUtils.displayToken(txt, a1); AnalyzerUtils.displayToken(txt, a2); AnalyzerUtils.displayToken(txt, a3); AnalyzerUtils.displayToken(txt, a4); AnalyzerUtils.displayToken(txt, a5); }结果:
my|blog|我|来|自|湖|北|武|汉| my|blog|我来自湖北|武汉| this|is|my|blog|我来自湖北|武汉| this|is|my|blog,我来自湖北,武汉| this|is|my|blog|我|来自|湖北|武汉|
@Test public void test02() { String txt = "this is my blog,我来自湖北,武汉"; Analyzer a1 = new StandardAnalyzer(Version.LUCENE_35); Analyzer a2 = new StopAnalyzer(Version.LUCENE_35); Analyzer a3 = new SimpleAnalyzer(Version.LUCENE_35); Analyzer a4 = new WhitespaceAnalyzer(Version.LUCENE_35); System.out.println(AnalyzerUtils.class.getResource("/")); Analyzer a5 = new MMSegAnalyzer(new File("data/")); AnalyzerUtils.displayAllTokenInfo(txt, a1); System.out.println("***************************"); AnalyzerUtils.displayAllTokenInfo(txt, a2); System.out.println("***************************"); AnalyzerUtils.displayAllTokenInfo(txt, a3); System.out.println("***************************"); AnalyzerUtils.displayAllTokenInfo(txt, a4); System.out.println("***************************"); AnalyzerUtils.displayAllTokenInfo(txt, a5); }结果:
3: |my|---offset[8-10]---type:<ALPHANUM> 1: |blog|---offset[11-15]---type:<ALPHANUM> 1: |我|---offset[16-17]---type:<IDEOGRAPHIC> 1: |来|---offset[17-18]---type:<IDEOGRAPHIC> 1: |自|---offset[18-19]---type:<IDEOGRAPHIC> 1: |湖|---offset[19-20]---type:<IDEOGRAPHIC> 1: |北|---offset[20-21]---type:<IDEOGRAPHIC> 1: |武|---offset[22-23]---type:<IDEOGRAPHIC> 1: |汉|---offset[23-24]---type:<IDEOGRAPHIC> *************************** 3: |my|---offset[8-10]---type:word 1: |blog|---offset[11-15]---type:word 1: |我来自湖北|---offset[16-21]---type:word 1: |武汉|---offset[22-24]---type:word *************************** 1: |this|---offset[0-4]---type:word 1: |is|---offset[5-7]---type:word 1: |my|---offset[8-10]---type:word 1: |blog|---offset[11-15]---type:word 1: |我来自湖北|---offset[16-21]---type:word 1: |武汉|---offset[22-24]---type:word *************************** 1: |this|---offset[0-4]---type:word 1: |is|---offset[5-7]---type:word 1: |my|---offset[8-10]---type:word 1: |blog,我来自湖北,武汉|---offset[11-24]---type:word *************************** 1: |this|---offset[0-4]---type:letter 1: |is|---offset[5-7]---type:letter 1: |my|---offset[8-10]---type:letter 1: |blog|---offset[11-15]---type:letter 1: |我|---offset[16-17]---type:word 1: |来自|---offset[17-19]---type:word 1: |湖北|---offset[19-21]---type:word 1: |武汉|---offset[22-24]---type:word
@Test public void test03() { Analyzer a1 = new MyStopAnalyzer(new String[] { "I", "you" }); System.out.println(StopAnalyzer.ENGLISH_STOP_WORDS_SET); // 原停用词 // [but, be, with, such, then, for, no, will, not, are, and, their, if, // this, on, into, a, or, there, in, that, they, // was, is, it, an, the, as, at, these, by, to, of] String txt = "how are you thank you, I hate you"; AnalyzerUtils.displayToken(txt, a1); }结果:
[but, be, with, such, then, for, no, will, not, are, and, their, if, this, on, into, a, or, there, i n, that, they, was, is, it, an, the, as, at, these, by, to, of] how|thank|hate|
@Test public void test04() { try { Analyzer a2 = new MySameAnalyzer(); String txt = "this is my blog,我来自中国湖北,武汉"; Directory dir = new RAMDirectory(); IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig( Version.LUCENE_35, a2)); Document doc = new Document(); doc.add(new Field("content", txt, Field.Store.YES, Field.Index.ANALYZED)); writer.addDocument(doc); writer.close(); IndexSearcher searcher = new IndexSearcher(IndexReader.open(dir)); TopDocs tds = searcher.search(new TermQuery( new Term("content", "天朝")), 10); Document d = searcher.doc(tds.scoreDocs[0].doc); System.out.println(d.get("content")); AnalyzerUtils.displayAllTokenInfo(txt, a2); } catch (CorruptIndexException e) { e.printStackTrace(); } catch (LockObtainFailedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }结果:
this is my blog,我来自中国湖北,武汉 ----------1 1: |this|---offset[0-4]---type:letter this----------1 1: |is|---offset[5-7]---type:letter is----------1 1: |my|---offset[8-10]---type:letter my----------1 1: |blog|---offset[11-15]---type:letter blog----------1 1: |我|---offset[16-17]---type:word 我----------1 1: |来自|---offset[17-19]---type:word 来自----------1 1: |中国|---offset[19-21]---type:word 0: |大陆|---offset[19-21]---type:word 0: |天朝|---offset[19-21]---type:word 天朝----------0 1: |湖北|---offset[21-23]---type:word 湖北----------1 1: |武汉|---offset[24-26]---type:word 武汉----------1