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