一、SynonymFilter的使用



Lucene42SynonmFilter使用方法是很简单的;看下面这个demo就可以明白了。

package com.xh.lucene;
import java.util.Map;
import java.util.HashMap;
import java.io.StringReader;
import java.io.IOException;
importorg.apache.lucene.analysis.core.StopAnalyzer;
import org.apache.lucene.analysis.core.StopFilter;
importorg.apache.lucene.analysis.core.LowerCaseFilter;
importorg.apache.lucene.analysis.core.WhitespaceTokenizer;
importorg.apache.lucene.analysis.standard.StandardTokenizer;
import org.apache.lucene.analysis.synonym.SynonymFilterFactory;
importorg.apache.lucene.analysis.TokenStream;
importorg.apache.lucene.analysis.tokenattributes.CharTermAttribute;
importorg.apache.lucene.analysis.util.FilesystemResourceLoader;
import org.apache.lucene.util.Version;
import org.apache.lucene.util.CharsRef;
public final class SynonymFilterDemo {
private static voiddisplayTokens(TokenStream ts) throws IOException {
CharTermAttribute termAttr = ts.addAttribute(CharTermAttribute.class);
ts.reset();
while(ts.incrementToken()) {
String token = termAttr.toString();
System.out.print("[" + token + "] ");
}
System.out.println();
ts.end();
ts.close();
}
public static voidmain(String[] args) throws Exception {
String testinput = "aaa fooaaa baraaa bazaaa GB gib gigabytegigabytes";
Version ver=Version.LUCENE_42;
String synfile="synonyms.txt";
Map filterargs=new HashMap();
filterargs.put("luceneMatchVersion",ver.toString());
filterargs.put("synonyms", synfile);
filterargs.put("ignoreCase", "true");
filterargs.put("format", "solr");
filterargs.put("expand", "false");
SynonymFilterFactory factory= new SynonymFilterFactory();
factory.setLuceneMatchVersion(Version.LUCENE_42);
factory.init(filterargs);
factory.inform(new FilesystemResourceLoader());
WhitespaceTokenizer tokenizer =
new WhitespaceTokenizer(ver, new StringReader(testinput));
TokenStream ts = factory.create(tokenizer);
//ts =new LowerCaseFilter(ver,tokenizer);
//ts =new StopFilter(ver, ts,
//StopAnalyzer.ENGLISH_STOP_WORDS_SET);
displayTokens(ts);
}
}

其中synonyms.txtlucene的源代码

lucene\analysis\common\src\test\org\apache\lucene\analysis\synonym\synonyms.txt

synonyms.txt复制到项目根目录下就可以了。

运行结果如下:

[aaaa] [fooaaa] [fooaaa] [fooaaa] [gb] [gb] [gb][gb]



如果把 filterargs.put("expand","false");换成filterargs.put("expand", "true");

则结果如下:

[aaaa] [fooaaa] [baraaa] [bazaaa] [fooaaa] [baraaa] [bazaaa][fooaaa] [baraaa] [bazaaa] [gb] [gib] [gigabyte] [gigabytes] [gb] [gib][gigabyte] [gigabytes] [gb] [gib] [gigabyte] [gigabytes] [gb] [gib] [gigabyte][gigabytes]



了解这两者之间的差别,我们需要先了解lucene中同义词典的两种格式


格式一:aaa => aaaa


格式二:fooaaa,baraaa,bazaaa



格式一表示如果文本中出现了aaa,那么直接替换成aaaaexpand参数对此没有影响。


格式二表示如果文本中出现了fooaaa或者baraaa或者bazaaa,当expand=true时,把待替换的文本用fooaaabaraaa bazaaa替换;当expand=false时,把待替换文本用第一个单词即fooaaa替换。


这些不同点自己跑一遍就都明白了。



二、SynonymFilter的实现原理



Lucene中SynonymFilter的实现是很精巧的。运用了Hash表和FST(Finite State Transducer)两种数据结构。


Hash表存储了同义词典。


FST可以视为SortedMap


Word表示可以被查询到的词,Pos表示Word对应的同义词在Hash表中的位置。


FSTFSA/FSM的一种变形。 FST的原理可以参考有限自动机。



简单地说,在SynonymFilterFSTinput是词,output是词对应同义词在Hash表中的位置。



例如:有abandon,desert, forsake, leave这组同义词,我们的处理方式是expand


SynonymMap的处理逻辑如下:


第一步:把abandon,desert,forsake,leave作为整体存储到Hash表中。并记录存储起始位置pos


第二步,构造的map如下:


Map.put(“abandon”,pos);


Map.put(“desert”,pos);


Map.put(“forsake”,pos);


Map.put(“leave”,pos);



这样的话,只要在Map中找到的词,都用Hash中存储的词替换。由于HashMap本身是用Hash表实现的,空间浪费比较严重。而字符串之间的前缀和后缀信息可以对数据进行压缩,所以用FST存储字符串的优点就出来了。(当然,本例的字符串之间没有公共的前缀和后缀,这只是一个jok