1.首先创建同义词过滤器
package synonymous; import org.apache.lucene.analysis.TokenFilter; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Stack; import java.util.TreeMap; /** * 同义词过滤器设计思路: * 1.取出分好的语汇单词 * 2.查同义词表,若有同义词,则在相同位置添加同义词 * (1)若有同义词,入栈,记录当前状态 * (2)跳转到下一个元素 * (3)恢复前一状态 * (4)同义词出栈,在同一位置存储同义词(要设置PositionIncrementAttribute属性为0) */ public final class MySynonymousFilter extends TokenFilter { /** * cta是索引中的语汇单元,不是真实的数据 */ private CharTermAttribute cta = null; /** * pia记录分词过后的语汇单词之间的距离 */ private PositionIncrementAttribute pia = null; /** * 用于记录当前状态(等跳转到下一元素之后,可以通过恢复状态,来执行添加前一元素的同义词,而不覆盖掉前一元素) */ private State currentState = null; /** * 栈,存储前一元素的所以同义词(好处,每次取出当前同义词后(执行操作完毕后),就需要删除掉当前同义词,处理下一同义词) */ private Stack<String> synonymousWordStack = null; protected MySynonymousFilter(TokenStream input) { super(input); //从TokenStream中取出分好的语汇单词 cta = input.addAttribute(CharTermAttribute.class); //从TokenStream中取出分好的语汇单词的距离 pia = input.addAttribute(PositionIncrementAttribute.class); synonymousWordStack = new Stack<String>(); } @Override public boolean incrementToken() throws IOException { // if (cta.toString().equals("我")){ // //清空cta所在位置数据(此时,cta已是索引中的数据,不再是真实数据!!) // cta.setEmpty(); // //在cta所在位置追加 // cta.append("咱"); // } /** * 尝试一 * 问题:若在相同位置添加同义词,则原来的词汇会被覆盖掉? * 解决办法:先记录当前状态,然后移动到以下一位置,再恢复原状态,进行添加同义词操作。 * 原因:因为是通过记录的状态进行操作的,原位置数据并没有被改变,这样就保留了原数据 * 具体操作: * 1.添加状态变量 private State currentState,并捕获当前状态 * 2.移动到下一个元素 * 问题:现在我是通过获取同义词数组,判断数组是否为空,然后进行处理,这样,却无法实现跳转到下一元素 * 再进行处理? * 解决办法:定义一个成员变量:栈,每次进入之前,先判断前一次同义词栈是否为空,不为空,则进行处理。 * 具体操作: * 1.添加成员变量 private Stack<String> synonymousWordStack 同义词栈 * 2.将当前元素的所有的同义词入栈 */ //查询同义词组 // String[] sws = getSynonymousWords(cta.toString()); // //代表有同义词 // if (sws!= null){ // //只要有同义词,则捕获当前状态 // currentState = captureState(); // for (String str:sws){ // cta.setEmpty(); // cta.append(str); // } // } // /** * 每次进入之前,先判断栈是否为空,不为空,则上一个词有待处理的同义词 * 注意:这里的栈,是前面的元素若有同义词的话,会将同义词入栈 * 为什么这么做? * 原因:因为加同义词的正确操作步骤是,先判断是否有同义词,若有则先保存当前状态,然后跳转到下一元素,再恢复状态, * 最后处理同义词,而栈就是我们实现判断是否有同义词的工具。 */ if (!synonymousWordStack.isEmpty()){ //1.先恢复前一状态(此时的cta代表当前元素的前一个元素) restoreState(currentState); //2.处理栈中存储的同义词 //2.1取出栈顶元素 String str = synonymousWordStack.pop(); //2.2当前cta置空(注意:这里的cta已经写到TokenStream当中,这里只是一个状态数据,所以覆盖了也没事) cta.setEmpty(); //2.3添加同义词 cta.append(str); //2.4设置位置增量为0(和原cta在同一位置) pia.setPositionIncrement(0); //2.5处理一个,返回一个,否则,每次处理占中 /** * 注意:处理一个,返回一个true!! * 原因:因为假如栈中的元素超过一个,那么每处理栈中元素一次,都会调用this.input.incrementToken()一次,这样会将 * 元素向前推进,等处理栈中元素完毕之后,中间已倍推进的元素将无法在进行同义词处理(改成while,那么有多 * 个同义词,只会剩下最后一个) */ return true; } /** * 判断当前是否还有元素,没有元素,则返回false * input是父类TokenFilter的成员变量,从Tokenizer或者其他的TokenFilter传递过来的就是这个input(其实就是TokenStream) * 注意: * incrementToken()相当于迭代器,取出本下标的值,并往后推移一位 * 特别注意: * 必须等栈中的所有元素处理完毕之后,再进行this.input.incrementToken()判断! * 原因:因为假如栈中的元素超过一个,那么再循环的时候this.input.incrementToken()会将元素向前推进,等处理 * 栈中元素完毕之后,中间已倍推进的元素将无法在进行同义词处理 */ if (!this.input.incrementToken()){ return false; } //若当前cta所对应的词有同义词,则保存状态,等待后续使用 if (getSynonymousWords(cta.toString())){ currentState = captureState(); } return true; } //获取同义词 private boolean getSynonymousWords(String key){ Map<String,String[]> map = new HashMap<String, String[]>(); map.put("我",new String[]{"咱","俺"}); map.put("中国",new String[]{"大陆","天朝"}); String[] sws = map.get(key); //将key所对应的所有的同义词入栈 if (sws!=null && sws.length>0){ for (String str:sws){ synonymousWordStack.push(str); } //代表有同义词 return true; } //代表无同义词 return false; } }
2.其次创建同义词分词器
package synonymous; import com.chenlb.mmseg4j.Dictionary; import com.chenlb.mmseg4j.MaxWordSeg; import com.chenlb.mmseg4j.analysis.MMSegTokenizer; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; import java.io.Reader; public final class MySynonymousAnalyzer extends Analyzer{ @Override public TokenStream tokenStream(String fieldName, Reader reader) { /** * 基本思路: * 1.首先用new MMSegTokenizer(new MaxWordSeg(Dictionary.getInstance()), reader)对输入流“Reader”进行分词 * 2.然后使用自定义的同义词过滤器“MySynonymousFilter”进行订制过滤 */ return new MySynonymousFilter(new MMSegTokenizer(new MaxWordSeg(Dictionary.getInstance()), reader)); } }
3.最后使用同义词分词器检索
@Test public void testMySynonymousAnalyzer(){ String str = "我,潘畅,来自中国江苏省泗洪县峰山乡邮局西侧50米"; Analyzer analyzer = new MySynonymousAnalyzer(); // AnalyzerUtils.displayToken(str, analyzer); search(str, analyzer); } private void search(String str, Analyzer analyzer){ try { Directory directory = new RAMDirectory(); IndexWriter indexWriter = new IndexWriter(directory,new IndexWriterConfig(Version.LUCENE_35,analyzer)); Document document = new Document(); document.add(new Field("content", str, Field.Store.YES, Field.Index.ANALYZED)); indexWriter.addDocument(document); indexWriter.close(); IndexReader indexReader = IndexReader.open(directory); IndexSearcher indexSearcher = new IndexSearcher(indexReader); Query query = new TermQuery(new Term("content", "天朝")); TopDocs topDocs = indexSearcher.search(query, 10); ScoreDoc[] scoreDocs = topDocs.scoreDocs; if (scoreDocs != null && scoreDocs.length>0){ Document doc = indexSearcher.doc(scoreDocs[0].doc); System.out.println(doc.get("content")); } else { System.out.println("搜不到!"); } indexReader.close(); } catch (IOException e) { e.printStackTrace(); } }